diff --git a/docs/src/catalyst-standards/draft-cips/c509-plutus-restricted-certificate/c509-cert-plutus-restricted.cddl b/docs/src/catalyst-standards/draft-cips/c509-plutus-restricted-certificate/c509-cert-plutus-restricted.cddl index 4967ed331e1..69eed33abe7 100644 --- a/docs/src/catalyst-standards/draft-cips/c509-plutus-restricted-certificate/c509-cert-plutus-restricted.cddl +++ b/docs/src/catalyst-standards/draft-cips/c509-plutus-restricted-certificate/c509-cert-plutus-restricted.cddl @@ -1,86 +1,64 @@ ; This c509 Certificate format is based upon: -; https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/ +; https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/ ; And is restricted/customized to better enable compatibility with Plutus scripts -; that would consume them, without loosing necessary features of x509 +; that would consume them, without losing necessary features of x509 ; Not all x509 features are supported and some fields have different semantics to improve ; certificate size and ability to be processed by Plutus Scripts. -; cspell: words reencoded, biguint +; cspell: words reencoded, biguint, stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw -C509CertificatePlutusRestrictedSubset = [ TBSCertificate, issuerSignatureValue: ed25519Signature, ] +C509CertificatePlutusRestrictedSubset = [ + TBSCertificate, + issuerSignatureValue: ed25519Signature +] ; The elements of the following group are used in a CBOR Sequence: TBSCertificate = ( - c509CertificateType: &c509CertificateTypeValues, ; Always 0 - certificateSerialNumber: CertificateSerialNumber, ; Can be ignored/set to 0 or used as intended. - issuer: Name, ; This could be an on-chain reference to the issuer cert, what would be the best way? Transaction hash/cert hash? - validityNotBefore: Time, ; c509 uses UTC - validityNotAfter: Time, ; c509 uses UTC - subject: Name, ; Reference to on-chain keys related to this certificate - subjectPublicKeyAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519 - subjectPublicKey: subjectPublicKey, ; Ed25519 public key - extensions: Extensions, ; No extensions are currently supported must be set to [] - issuerSignatureAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519 + c509CertificateType: int, ; Always 2 as a natively signed + certificateSerialNumber: CertificateSerialNumber, ; Can be ignored/set to 0 or used as intended. + issuerSignatureAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519 + issuer: Name / null, ; If the 'issuer' field is identical to the 'subject' field (in case of self-signed), then it must be encoded as CBOR null + ; This could be an on-chain reference to the issuer cert. What would be the best way? Transaction hash/cert hash? + validityNotBefore: ~time, ; c509 uses UTC + validityNotAfter: ~time / null, ; c509 uses UTC, no expiration date must be set to null + subject: Name, ; Reference to on-chain keys related to this certificate + subjectPublicKeyAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519 + subjectPublicKey: subjectPublicKey, ; Ed25519 public key + extensions: Extensions ; Set to [] if no Extensions provided ) -; 0 = Native CBOR Certificate type -; 1 = reencoded-der-cert - Not supported in this restricted version of the format. -c509CertificateTypeValues = ( native-cbor: 0, - ; reencoded-der: 1 ; Not supported in this restricted encoding format -) - -CertificateSerialNumber = biguint - -Name = [ * RelativeDistinguishedName ] - / text - / bytes - -RelativeDistinguishedName = Attribute / [ 2* Attribute ] +CertificateSerialNumber = ~biguint -Attribute = ( - ( attributeType: int, attributeValue: text ) - // ( attributeType: oid, attributeValue: bytes ) - // ( attributeType: pen, attributeValue: bytes ) - // CardanoPublicKey -) - -subjectPublicKey = bytes .size (32..32); Ed25519 public key stored in bytes, adjust size of this if other key types are supported. +; Currently ONLY AlgorithmIdentifier int(12) - Ed25519 is supported. +; oid and [ algorithm: oid, parameters: bytes ] are not supported by Plutus. +AlgorithmIdentifier = int / ~oid / [ algorithm: ~oid, parameters: bytes ] -; This is a completely custom Attribute for the RelativeDistinguishedName which is only for use with Plutus scripts. -; attributeType = The type of Cardano key we associate with this certificate. -; proof = Does the transaction require proof that the key is owned by the transaction signer? -; attributeValue = The Cardano public key hash of the attribute type +Name = [ * Attribute ] / text / bytes -CardanoPublicKey = ( attributeType: &cardanoKeyTypes proof: bool, attributeValue: bytes .size (28..28) ) +Attribute = ( attributeType: int, attributeValue: text ) + // ( attributeType: ~oid, attributeValue: bytes ) -cardanoKeyTypes = ( - paymentKeyHash: 0, - stakeKeyHash: 1, - drepVerificationKeyHash: 2, - ccColdVerificationKeyHash: 3, - ccHotVerificationKeyHash: 4, -) +subjectPublicKey = bytes .size (32..32) ; Ed25519 public key stored in bytes, adjust size if other key types are supported. -; Plutus will need to convert the Unix epoch timestamp to the nearest slot number +; For ~time, Plutus will need to convert the Unix epoch timestamp to the nearest slot number ; validityNotBefore rounds up to the next Slot after that time. ; validityNotAfter rounds down to the next Slot before that time. -Time = ( ~time / null ) - -ed25519Signature = bstr .size 64; Ed25519 signature must be tagged to identify their type. +ed25519Signature = bstr .size 64 ; Ed25519 signature must be tagged to identify their type. -; Currently ONLY AlgorithmIdentifier int(12) - Ed25519 is supported. -; oid and [ algorithm: oid, parameters: bytes ] are not supported by Plutus. -AlgorithmIdentifier = (int - / ~oid - / [ algorithm: ~oid, parameters: bytes ]) +; The only Extension supported is int(3) = SubjectAltName where GeneralNames need to be +; int(6) = uniformResourceIdentifier. +; This uniformResourceIdentifier must conform to the URI based line in CIP-0134: +; https://github.com/input-output-hk/catalyst-CIPs/tree/cip13-simple-cardano-address-extension/CIP-0134 +; for example, web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw -; Extensions are not currently supported by plutus and should be set to [] -; Any extensions present in the certificate will be ignored by plutus scripts. Extensions = [ * Extension ] / int Extension = ( - ( extensionID: int, extensionValue: any ) - // ( extensionID: ~oid, ? critical: true, extensionValue: bytes ) - // ( extensionID: pen, ? critical: true, extensionValue: bytes ) + ( extensionID: int, extensionValue: any ) + // ( extensionID: ~oid, ? critical: true, extensionValue: bytes ) ) + +SubjectAltName = GeneralNames / text +GeneralNames = [ + GeneralName ] +GeneralName = ( GeneralNameType: int, GeneralNameValue: any ) \ No newline at end of file diff --git a/rust/Justfile b/rust/Justfile index abddacc2617..6d7e59495ad 100644 --- a/rust/Justfile +++ b/rust/Justfile @@ -26,8 +26,8 @@ code-format: # Lint the rust code code-lint: - cargo lintfix - cargo lint + cargo lintfix -r + cargo lint -r # Pre Push Checks pre-push: sync-cfg code-format code-lint license-check diff --git a/rust/c509-certificate/examples/cli/data/cert_sample_1.json b/rust/c509-certificate/examples/cli/data/cert_sample_1.json index ca41ddabfab..f7ed50eb45d 100644 --- a/rust/c509-certificate/examples/cli/data/cert_sample_1.json +++ b/rust/c509-certificate/examples/cli/data/cert_sample_1.json @@ -1,7 +1,8 @@ { "self_signed": true, - "c509_certificate_type": 0, + "c509_certificate_type": 2, "certificate_serial_number": 128269, + "issuer_signature_algorithm": null, "issuer": [ { "oid": "2.5.4.3", @@ -24,6 +25,5 @@ "value": { "int": 1 }, "critical": false } - ], - "issuer_signature_algorithm": null + ] } diff --git a/rust/c509-certificate/examples/cli/main.rs b/rust/c509-certificate/examples/cli/main.rs index 816446f487e..ddc750f8e78 100644 --- a/rust/c509-certificate/examples/cli/main.rs +++ b/rust/c509-certificate/examples/cli/main.rs @@ -8,13 +8,14 @@ use std::{ use asn1_rs::{oid, Oid}; use c509_certificate::{ + attributes::attribute::Attribute, big_uint::UnwrappedBigUint, + cert_tbs::TbsCert, extensions::Extensions, issuer_sig_algo::IssuerSignatureAlgorithm, - name::{rdn::RelativeDistinguishedName, Name, NameValue}, + name::{Name, NameValue}, signing::{PrivateKey, PublicKey}, subject_pub_key_algo::SubjectPubKeyAlgorithm, - tbs_cert::TbsCert, time::Time, }; use chrono::{DateTime, Utc}; @@ -97,22 +98,25 @@ impl Cli { struct C509Json { /// Indicate whether the certificate is self-signed. self_signed: bool, - /// Optional certificate type, if not provided, set to 0 as self-signed. + /// Optional certificate type, if not provided, set to 2 as self-signed. certificate_type: Option, /// Optional serial number of the certificate, /// if not provided, a random number will be generated. serial_number: Option, + /// Optional issuer signature algorithm of the certificate, + /// if not provided, set to Ed25519. + issuer_signature_algorithm: Option, /// Optional issuer of the certificate, /// if not provided, issuer is the same as subject. - issuer: Option, + issuer: Option>, /// Optional validity not before date, /// if not provided, set to current time. validity_not_before: Option, /// Optional validity not after date, /// if not provided, set to no expire date 9999-12-31T23:59:59+00:00. validity_not_after: Option, - /// Relative distinguished name of the subject. - subject: RelativeDistinguishedName, + /// Attributes of the subject. + subject: Vec, /// Optional subject public key algorithm of the certificate, /// if not provided, set to Ed25519. subject_public_key_algorithm: Option, @@ -121,9 +125,6 @@ struct C509Json { subject_public_key: String, /// Extensions of the certificate. extensions: Extensions, - /// Optional issuer signature algorithm of the certificate, - /// if not provided, set to Ed25519. - issuer_signature_algorithm: Option, /// Optional issuer signature value of the certificate. #[serde(skip_deserializing)] issuer_signature_value: Option>, @@ -133,9 +134,9 @@ struct C509Json { const ED25519: (Oid, Option) = (oid!(1.3.101 .112), None); /// Integer indicate that certificate is self-signed. -/// 0 for Natively Signed C509 Certificate following X.509 v3 -/// 1 for CBOR re-encoding of X.509 v3 Certificate -const SELF_SIGNED_INT: u8 = 0; +/// 2 for Natively Signed C509 Certificate following X.509 v3 +/// 3 for CBOR re-encoding of X.509 v3 Certificate +const SELF_SIGNED_INT: u8 = 2; // -------------------generate----------------------- @@ -159,7 +160,12 @@ fn generate( // Parse validity dates or use defaults // Now for not_before date - let not_before = parse_or_default_date(c509_json.validity_not_before, Utc::now().timestamp())?; + let now_timestamp: u64 = Utc::now() + .timestamp() + .try_into() + .map_err(|_| anyhow::anyhow!("Current timestamp is invalid"))?; + + let not_before = parse_or_default_date(c509_json.validity_not_before, now_timestamp)?; // Default as expire date for not_after // Expire date = 9999-12-31T23:59:59+00:00 as mention in the C509 document let not_after = parse_or_default_date( @@ -175,18 +181,18 @@ fn generate( let tbs = TbsCert::new( c509_json.certificate_type.unwrap_or(SELF_SIGNED_INT), serial_number, - Name::new(NameValue::RelativeDistinguishedName(issuer)), + c509_json + .issuer_signature_algorithm + .unwrap_or(IssuerSignatureAlgorithm::new(key_type.0.clone(), ED25519.1)), + Some(Name::new(NameValue::Attribute(issuer))), Time::new(not_before), Time::new(not_after), - Name::new(NameValue::RelativeDistinguishedName(c509_json.subject)), + Name::new(NameValue::Attribute(c509_json.subject)), c509_json .subject_public_key_algorithm - .unwrap_or(SubjectPubKeyAlgorithm::new(key_type.0.clone(), key_type.1)), + .unwrap_or(SubjectPubKeyAlgorithm::new(key_type.0, key_type.1)), public_key.to_bytes(), c509_json.extensions.clone(), - c509_json - .issuer_signature_algorithm - .unwrap_or(IssuerSignatureAlgorithm::new(key_type.0, ED25519.1)), ); let cert = c509_certificate::generate(&tbs, private_key)?; @@ -213,9 +219,8 @@ fn write_to_output_file(output: PathBuf, data: &[u8]) -> anyhow::Result<()> { /// If self-signed is true, issuer is the same as subject. /// Otherwise, issuer must be present. fn determine_issuer( - self_signed: bool, issuer: Option, - subject: RelativeDistinguishedName, -) -> anyhow::Result { + self_signed: bool, issuer: Option>, subject: Vec, +) -> anyhow::Result> { if self_signed { Ok(subject) } else { @@ -229,7 +234,7 @@ fn validate_certificate_type( ) -> anyhow::Result<()> { if self_signed && certificate_type.unwrap_or(SELF_SIGNED_INT) != SELF_SIGNED_INT { return Err(anyhow::anyhow!( - "Certificate type must be 0 if self-signed is true" + "Certificate type must be {SELF_SIGNED_INT} if self-signed is true" )); } Ok(()) @@ -249,13 +254,17 @@ fn get_key_type(key_type: &Option) -> anyhow::Result<(Oid<'static>, Opti } } -/// Parse date string to i64. -fn parse_or_default_date(date_option: Option, default: i64) -> Result { +/// Parse date string to u64. +fn parse_or_default_date(date_option: Option, default: u64) -> Result { match date_option { Some(date) => { DateTime::parse_from_rfc3339(&date) - .map(|dt| dt.timestamp()) - .map_err(|e| anyhow::anyhow!(format!("Failed to parse date {date}: {e}",))) + .map(|dt| { + dt.timestamp() + .try_into() + .map_err(|_| anyhow::anyhow!("Timestamp is invalid")) + })? + .map_err(|e| anyhow::anyhow!("Failed to parse date {date}: {e}")) }, None => Ok(default), } @@ -288,22 +297,26 @@ fn decode(file: &PathBuf, output: Option) -> anyhow::Result<()> { let mut d = minicbor::Decoder::new(&cert); let c509 = c509_certificate::c509::C509::decode(&mut d, &mut ())?; - let tbs_cert = c509.get_tbs_cert(); - let is_self_signed = tbs_cert.get_c509_certificate_type() == SELF_SIGNED_INT; + let tbs_cert = c509.tbs_cert(); + let is_self_signed = tbs_cert.c509_certificate_type() == SELF_SIGNED_INT; let c509_json = C509Json { self_signed: is_self_signed, - certificate_type: Some(tbs_cert.get_c509_certificate_type()), - serial_number: Some(tbs_cert.get_certificate_serial_number().clone()), - issuer: Some(extract_relative_distinguished_name(tbs_cert.get_issuer())?), - validity_not_before: Some(time_to_string(tbs_cert.get_validity_not_before().to_i64())?), - validity_not_after: Some(time_to_string(tbs_cert.get_validity_not_after().to_i64())?), - subject: extract_relative_distinguished_name(tbs_cert.get_subject())?, - subject_public_key_algorithm: Some(tbs_cert.get_subject_public_key_algorithm().clone()), + certificate_type: Some(tbs_cert.c509_certificate_type()), + serial_number: Some(tbs_cert.certificate_serial_number().clone()), + issuer_signature_algorithm: Some(tbs_cert.issuer_signature_algorithm().clone()), + issuer: Some(extract_attributes(tbs_cert.issuer())?), + validity_not_before: Some(time_to_string( + tbs_cert.validity_not_before().clone().into(), + )?), + validity_not_after: Some(time_to_string( + tbs_cert.validity_not_after().clone().into(), + )?), + subject: extract_attributes(tbs_cert.subject())?, + subject_public_key_algorithm: Some(tbs_cert.subject_public_key_algorithm().clone()), // Return a hex formation of the public key - subject_public_key: tbs_cert.get_subject_public_key().encode_hex(), - extensions: tbs_cert.get_extensions().clone(), - issuer_signature_algorithm: Some(tbs_cert.get_issuer_signature_algorithm().clone()), - issuer_signature_value: c509.get_issuer_signature_value().clone(), + subject_public_key: tbs_cert.subject_public_key().encode_hex(), + extensions: tbs_cert.extensions().clone(), + issuer_signature_value: c509.issuer_signature_value().clone(), }; let data = serde_json::to_string(&c509_json)?; @@ -316,18 +329,24 @@ fn decode(file: &PathBuf, output: Option) -> anyhow::Result<()> { Ok(()) } -/// Extract a `RelativeDistinguishedName` from a `Name`. -fn extract_relative_distinguished_name(name: &Name) -> anyhow::Result { - match name.get_value() { - NameValue::RelativeDistinguishedName(rdn) => Ok(rdn.clone()), - _ => Err(anyhow::anyhow!("Expected RelativeDistinguishedName")), +/// Extract a `Attributes` from a `Name`. +fn extract_attributes(name: &Name) -> anyhow::Result> { + match name.value() { + NameValue::Attribute(attrs) => Ok(attrs.clone()), + _ => Err(anyhow::anyhow!("Expected Attributes")), } } /// Convert time in i64 to string. -fn time_to_string(time: i64) -> anyhow::Result { - let datetime = - DateTime::from_timestamp(time, 0).ok_or_else(|| anyhow::anyhow!("Invalid timestamp"))?; +fn time_to_string(time: u64) -> anyhow::Result { + // Attempt to convert the timestamp and handle errors if they occur + let timestamp: i64 = time + .try_into() + .map_err(|e| anyhow::anyhow!("Failed to convert time: {:?}", e))?; + + // Convert the timestamp to a DateTime and handle any potential errors + let datetime = DateTime::from_timestamp(timestamp, 0) + .ok_or_else(|| anyhow::anyhow!("Invalid timestamp"))?; Ok(datetime.to_rfc3339()) } diff --git a/rust/c509-certificate/examples/web/index.js b/rust/c509-certificate/examples/web/index.js index dd37a6f83f1..a2bdca0cadf 100644 --- a/rust/c509-certificate/examples/web/index.js +++ b/rust/c509-certificate/examples/web/index.js @@ -1,11 +1,11 @@ // Testing the wasm binding JS functions. import init, { - generate, - verify, decode, - PublicKey, + generate, PrivateKey, + PublicKey, + verify, } from "../../pkg/c509_certificate.js"; const pem_sk = ` @@ -24,7 +24,7 @@ const tbs = { c509_certificate_type: 0, certificate_serial_number: 1000000n, issuer: { - relative_distinguished_name: [ + attributes: [ { oid: "2.5.4.3", value: [{ text: "RFC test CA" }], diff --git a/rust/c509-certificate/src/algorithm_identifier.rs b/rust/c509-certificate/src/algorithm_identifier.rs index 89bca4b118d..c30d7d32bf7 100644 --- a/rust/c509-certificate/src/algorithm_identifier.rs +++ b/rust/c509-certificate/src/algorithm_identifier.rs @@ -1,29 +1,34 @@ //! C509 Algorithm Identifier //! //! This module handle the `AlgorithmIdentifier` type where OID does not fall into the -//! table. +//! registry table. //! //! ```cddl //! AlgorithmIdentifier = int / ~oid / [ algorithm: ~oid, parameters: bytes ] //! ``` //! //! **Note** `AlgorithmIdentifier` that have the same OID with different parameters are -//! not implemented yet. +//! not supported yet. //! //! For more information about `AlgorithmIdentifier`, -//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) use asn1_rs::Oid; use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Serialize}; -use crate::oid::C509oid; - +use crate::{ + helper::{ + decode::{decode_array_len, decode_bytes, decode_datatype}, + encode::{encode_array_len, encode_bytes}, + }, + oid::C509oid, +}; /// A struct represents the `AlgorithmIdentifier` type. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct AlgorithmIdentifier { /// A `C509oid` - oid: C509oid, + c509_oid: C509oid, /// An optional parameter string param: Option, } @@ -33,18 +38,20 @@ impl AlgorithmIdentifier { #[must_use] pub fn new(oid: Oid<'static>, param: Option) -> Self { Self { - oid: C509oid::new(oid), + c509_oid: C509oid::new(oid), param, } } /// Get the OID. - pub(crate) fn get_oid(&self) -> Oid<'static> { - self.oid.clone().get_oid() + #[must_use] + pub fn oid(&self) -> &Oid<'static> { + self.c509_oid.oid() } /// Get the parameter. - pub(crate) fn get_param(&self) -> &Option { + #[must_use] + pub fn param(&self) -> &Option { &self.param } } @@ -56,13 +63,13 @@ impl Encode<()> for AlgorithmIdentifier { match &self.param { // [ algorithm: ~oid, parameters: bytes ] Some(p) => { - e.array(2)?; - self.oid.encode(e, ctx)?; - e.bytes(p.as_bytes())?; + encode_array_len(e, "Algorithm Identifier", 2)?; + self.c509_oid.encode(e, ctx)?; + encode_bytes(e, "Algorithm Identifier", p.as_bytes())?; }, // ~oid None => { - self.oid.encode(e, ctx)?; + self.c509_oid.encode(e, ctx)?; }, } Ok(()) @@ -72,21 +79,22 @@ impl Encode<()> for AlgorithmIdentifier { impl Decode<'_, ()> for AlgorithmIdentifier { fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { // [ algorithm: ~oid, parameters: bytes ] - if d.datatype()? == minicbor::data::Type::Array { - let len = d.array()?.ok_or(minicbor::decode::Error::message( - "Failed to get array length", - ))?; + if decode_datatype(d, "Algorithm Identifier")? == minicbor::data::Type::Array { + let len = decode_array_len(d, "Algorithm Identifier")?; if len != 2 { return Err(minicbor::decode::Error::message("Array length must be 2")); } let c509_oid = C509oid::decode(d, ctx)?; - let param = - String::from_utf8(d.bytes()?.to_vec()).map_err(minicbor::decode::Error::message)?; - Ok(AlgorithmIdentifier::new(c509_oid.get_oid(), Some(param))) + let param = String::from_utf8(decode_bytes(d, "Algorithm Identifier")?) + .map_err(minicbor::decode::Error::message)?; + Ok(AlgorithmIdentifier::new( + c509_oid.oid().clone(), + Some(param), + )) // ~oid } else { let oid = C509oid::decode(d, ctx)?; - Ok(AlgorithmIdentifier::new(oid.get_oid(), None)) + Ok(AlgorithmIdentifier::new(oid.oid().clone(), None)) } } } diff --git a/rust/c509-certificate/src/attributes/attribute.rs b/rust/c509-certificate/src/attributes/attribute.rs index 1aa99740a91..5eb871fd9e8 100644 --- a/rust/c509-certificate/src/attributes/attribute.rs +++ b/rust/c509-certificate/src/attributes/attribute.rs @@ -2,12 +2,18 @@ //! //! ```cddl //! Attribute = ( attributeType: int, attributeValue: text ) // -//! ( attributeType: ~oid, attributeValue: bytes ) // -//! ( attributeType: pen, attributeValue: bytes ) +//! ( attributeType: ~oid, attributeValue: bytes ) // +//! ``` +//! +//! In some case attributeValue can have multiple values. +//! +//! ```cddl +//! Attributes = ( attributeType: int, attributeValue: [+text] ) // +//! ( attributeType: ~oid, attributeValue: [+bytes] ) //! ``` //! //! For more information about Attribute, -//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) use std::str::FromStr; @@ -16,8 +22,13 @@ use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Deserializer, Serialize}; use super::data::{get_oid_from_int, ATTRIBUTES_LOOKUP}; -use crate::oid::{C509oid, C509oidRegistered}; - +use crate::{ + helper::{ + decode::{decode_array_len, decode_datatype, decode_helper}, + encode::{encode_array_len, encode_helper}, + }, + oid::{C509oid, C509oidRegistered}, +}; /// A struct of C509 `Attribute` #[derive(Debug, Clone, PartialEq)] pub struct Attribute { @@ -40,28 +51,20 @@ impl Attribute { } } - /// Add a value to `Attribute`. - pub fn add_value(&mut self, value: AttributeValue) { - self.value.push(value); + /// Get the value of `Attribute`. + #[must_use] + pub fn value(&self) -> &[AttributeValue] { + &self.value } /// Get the registered OID of `Attribute`. - pub(crate) fn get_registered_oid(&self) -> &C509oidRegistered { + pub(crate) fn registered_oid(&self) -> &C509oidRegistered { &self.registered_oid } - /// Get the value of `Attribute`. - pub(crate) fn get_value(&self) -> &Vec { - &self.value - } - - /// Set whether `Attribute` can be PEN encoded. - pub(crate) fn set_pen_supported(self) -> Self { - Self { - registered_oid: self.registered_oid.pen_encoded(), - multi_value: self.multi_value, - value: self.value, - } + /// Add a value to `Attribute`. + pub fn add_value(&mut self, value: AttributeValue) { + self.value.push(value); } /// Set whether `Attribute` can have multiple value. @@ -98,7 +101,7 @@ impl Serialize for Attribute { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { let helper = Helper { - oid: self.registered_oid.get_c509_oid().get_oid().to_string(), + oid: self.registered_oid().c509_oid().oid().to_string(), value: self.value.clone(), }; helper.serialize(serializer) @@ -112,14 +115,14 @@ impl Encode<()> for Attribute { // Encode CBOR int if available if let Some(&oid) = self .registered_oid - .get_table() + .table() .get_map() - .get_by_right(&self.registered_oid.get_c509_oid().get_oid()) + .get_by_right(self.registered_oid().c509_oid().oid()) { - e.i16(oid)?; + encode_helper(e, "Attribute as OID int", ctx, &oid)?; } else { - // Encode unwrapped CBOR OID or CBOR PEN - self.registered_oid.get_c509_oid().encode(e, ctx)?; + // Encode unwrapped CBOR OID + self.registered_oid().c509_oid().encode(e, ctx)?; } // Check if the attribute value is empty @@ -129,7 +132,7 @@ impl Encode<()> for Attribute { // If multi-value attributes, encode it as array if self.multi_value { - e.array(self.value.len() as u64)?; + encode_array_len(e, "Attribute multiple value", self.value.len() as u64)?; } // Encode each value in the attribute @@ -144,22 +147,20 @@ impl Encode<()> for Attribute { impl Decode<'_, ()> for Attribute { fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { // Handle CBOR int - let mut attr = if d.datatype()? == minicbor::data::Type::U8 { - let i = d.i16()?; + let mut attr = if decode_datatype(d, "Attribute as OID int")? == minicbor::data::Type::U8 { + let i = decode_helper(d, "Attribute as OID int", ctx)?; let oid = get_oid_from_int(i).map_err(minicbor::decode::Error::message)?; Attribute::new(oid.clone()) } else { - // Handle unwrapped CBOR OID or CBOR PEN + // Handle unwrapped CBOR OID let c509_oid: C509oid = d.decode()?; - Attribute::new(c509_oid.get_oid()) + Attribute::new(c509_oid.oid().clone()) }; // Handle attribute value - if d.datatype()? == minicbor::data::Type::Array { + if decode_datatype(d, "Attribute")? == minicbor::data::Type::Array { // When multi-value attribute - let len = d.array()?.ok_or_else(|| { - minicbor::decode::Error::message("Failed to get array length for attribute value") - })?; + let len = decode_array_len(d, "Attribute multiple value")?; if len == 0 { return Err(minicbor::decode::Error::message("Attribute value is empty")); @@ -192,21 +193,33 @@ pub enum AttributeValue { impl Encode<()> for AttributeValue { fn encode( - &self, e: &mut Encoder, _ctx: &mut (), + &self, e: &mut Encoder, ctx: &mut (), ) -> Result<(), minicbor::encode::Error> { match self { - AttributeValue::Text(text) => e.str(text)?, - AttributeValue::Bytes(bytes) => e.bytes(bytes)?, + AttributeValue::Text(text) => encode_helper(e, "Attribute value", ctx, text)?, + AttributeValue::Bytes(bytes) => encode_helper(e, "Attribute value", ctx, bytes)?, }; Ok(()) } } impl Decode<'_, ()> for AttributeValue { - fn decode(d: &mut Decoder<'_>, _ctx: &mut ()) -> Result { - match d.datatype()? { - minicbor::data::Type::String => Ok(AttributeValue::Text(d.str()?.to_string())), - minicbor::data::Type::Bytes => Ok(AttributeValue::Bytes(d.bytes()?.to_vec())), + fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { + match decode_datatype(d, "Attribute value")? { + minicbor::data::Type::String => { + Ok(AttributeValue::Text(decode_helper( + d, + "Attribute value", + ctx, + )?)) + }, + minicbor::data::Type::Bytes => { + Ok(AttributeValue::Bytes(decode_helper( + d, + "Attribute value", + ctx, + )?)) + }, _ => { Err(minicbor::decode::Error::message( "Invalid AttributeValue, value should be either String or Bytes", @@ -233,7 +246,8 @@ mod test_attribute { attribute .encode(&mut encoder, &mut ()) .expect("Failed to encode Attribute"); - // Email Address example@example.com: 0x00736578616d706c65406578616d706c652e636f6d + // 1.2.840 .113549 .1 .9 .1 in attribute int = 0x00 + // Email Address example@example.com: 0x736578616d706c65406578616d706c652e636f6d assert_eq!( hex::encode(buffer.clone()), "00736578616d706c65406578616d706c652e636f6d" diff --git a/rust/c509-certificate/src/attributes/data.rs b/rust/c509-certificate/src/attributes/data.rs index b89fb429cb4..0ae826a3453 100644 --- a/rust/c509-certificate/src/attributes/data.rs +++ b/rust/c509-certificate/src/attributes/data.rs @@ -1,5 +1,5 @@ //! Attribute data provides a necessary information for encoding and decoding of C509 -//! Attribute. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! Attribute. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) //! Section 9.3 C509 Attributes Registry for more information. use anyhow::Error; diff --git a/rust/c509-certificate/src/attributes/mod.rs b/rust/c509-certificate/src/attributes/mod.rs index b188558e18a..d56ee25a245 100644 --- a/rust/c509-certificate/src/attributes/mod.rs +++ b/rust/c509-certificate/src/attributes/mod.rs @@ -2,33 +2,30 @@ //! //! ```cddl //! Attributes = ( attributeType: int, attributeValue: [+text] ) // -//! ( attributeType: ~oid, attributeValue: [+bytes] ) +//! ( attributeType: ~oid, attributeValue: [+bytes] ) //! ``` //! //! Use case: //! ```cddl -//! SubjectDirectoryAttributes = [+Attributes] +//! SubjectDirectoryAttributes = [+Attributes] //! ``` //! //! For more information about `Attributes`, -//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) use attribute::Attribute; use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; +use serde::{Deserialize, Serialize}; + +use crate::helper::{decode::decode_array_len, encode::encode_array_len}; pub mod attribute; mod data; /// A struct of C509 `Attributes` containing a vector of `Attribute`. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Attributes(Vec); -impl Default for Attributes { - fn default() -> Self { - Self::new() - } -} - impl Attributes { /// Create a new instance of `Attributes` as empty vector. #[must_use] @@ -36,13 +33,25 @@ impl Attributes { Self(Vec::new()) } + /// Get the attributes. + #[must_use] + pub fn attributes(&self) -> &[Attribute] { + &self.0 + } + /// Add an `Attribute` to the `Attributes`. /// and set `Attribute` value to support multiple value. - pub fn add_attr(&mut self, attribute: Attribute) { + pub fn add_attribute(&mut self, attribute: Attribute) { self.0.push(attribute.set_multi_value()); } } +impl Default for Attributes { + fn default() -> Self { + Self::new() + } +} + impl Encode<()> for Attributes { fn encode( &self, e: &mut Encoder, ctx: &mut (), @@ -52,7 +61,8 @@ impl Encode<()> for Attributes { "Attributes should not be empty", )); } - e.array(self.0.len() as u64)?; + // The attribute type should be included in array too + encode_array_len(e, "Attributes", self.0.len() as u64 * 2)?; for attribute in &self.0 { attribute.encode(e, ctx)?; } @@ -61,19 +71,18 @@ impl Encode<()> for Attributes { } impl Decode<'_, ()> for Attributes { - fn decode(d: &mut Decoder<'_>, _ctx: &mut ()) -> Result { - let len = d - .array()? - .ok_or_else(|| minicbor::decode::Error::message("Failed to get array length"))?; + fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { + let len = decode_array_len(d, "Attributes")?; if len == 0 { return Err(minicbor::decode::Error::message("Attributes is empty")); } let mut attributes = Attributes::new(); - for _ in 0..len { - let attribute = Attribute::decode(d, &mut ())?; - attributes.add_attr(attribute); + // The attribute type is included in an array, so divide by 2 + for _ in 0..len / 2 { + let attribute = Attribute::decode(d, ctx)?; + attributes.add_attribute(attribute); } Ok(attributes) @@ -97,17 +106,17 @@ mod test_attributes { attr.add_value(AttributeValue::Text("example@example.com".to_string())); attr.add_value(AttributeValue::Text("example@example.com".to_string())); let mut attributes = Attributes::new(); - attributes.add_attr(attr); + attributes.add_attribute(attr); attributes .encode(&mut encoder, &mut ()) .expect("Failed to encode Attributes"); - // 1 Attribute value (array len 1): 0x81 - // Email Address: 0x00 + // 1 Attribute (array len 2 (attribute type + value)): 0x82 + // Email Address attribute int: 0x00 // Attribute value (array len 2): 0x82 // example@example.com: 0x736578616d706c65406578616d706c652e636f6d assert_eq!( hex::encode(buffer.clone()), - "810082736578616d706c65406578616d706c652e636f6d736578616d706c65406578616d706c652e636f6d" + "820082736578616d706c65406578616d706c652e636f6d736578616d706c65406578616d706c652e636f6d" ); let mut decoder = Decoder::new(&buffer); diff --git a/rust/c509-certificate/src/big_uint.rs b/rust/c509-certificate/src/big_uint.rs index a28e93305d8..a6e67ad0a56 100644 --- a/rust/c509-certificate/src/big_uint.rs +++ b/rust/c509-certificate/src/big_uint.rs @@ -8,6 +8,7 @@ use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Serialize}; +use crate::helper::{decode::decode_bytes, encode::encode_bytes}; /// A struct representing an unwrapped CBOR unsigned bignum. #[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -21,6 +22,18 @@ impl UnwrappedBigUint { } } +impl From for UnwrappedBigUint { + fn from(value: u64) -> Self { + UnwrappedBigUint::new(value) + } +} + +impl From for u64 { + fn from(unwrapped_big_uint: UnwrappedBigUint) -> Self { + unwrapped_big_uint.0 + } +} + impl Encode<()> for UnwrappedBigUint { fn encode( &self, e: &mut Encoder, _ctx: &mut (), @@ -33,7 +46,7 @@ impl Encode<()> for UnwrappedBigUint { .copied() .collect::>(); - e.bytes(&significant_bytes)?; + encode_bytes(e, "Unwrapped big uint", &significant_bytes)?; Ok(()) } } @@ -41,8 +54,7 @@ impl Encode<()> for UnwrappedBigUint { impl Decode<'_, ()> for UnwrappedBigUint { fn decode(d: &mut Decoder<'_>, _ctx: &mut ()) -> Result { // Turn bytes into u64 - let b = d - .bytes()? + let b = decode_bytes(d, "Unwrapped big uint")? .iter() .fold(0, |acc, &b| (acc << 8) | u64::from(b)); Ok(UnwrappedBigUint::new(b)) @@ -54,7 +66,7 @@ mod test_big_uint { use super::*; - // Test reference https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/ + // Test reference https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/ // A.1. Example RFC 7925 profiled X.509 Certificate #[test] fn test_encode_decode() { @@ -65,6 +77,7 @@ mod test_big_uint { b_uint .encode(&mut encoder, &mut ()) .expect("Failed to encode UnwrappedBigUint"); + // 128269 (h'01F50D'): CBOR 0x4301f50d assert_eq!(hex::encode(buffer.clone()), "4301f50d"); let mut decoder = minicbor::Decoder::new(&buffer); @@ -74,7 +87,7 @@ mod test_big_uint { assert_eq!(decoded_b_uint, b_uint); } - // Test reference https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/ + // Test reference https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/ // A.2. Example IEEE 802.1AR profiled X.509 Certificate #[test] fn test_encode_decode_2() { @@ -85,6 +98,7 @@ mod test_big_uint { b_uint .encode(&mut encoder, &mut ()) .expect("Failed to encode UnwrappedBigUint"); + // 9112578475118446130 (h'7E7661D7B54E4632'): CBOR 0x487e7661d7b54e4632 assert_eq!(hex::encode(buffer.clone()), "487e7661d7b54e4632"); let mut decoder = minicbor::Decoder::new(&buffer); diff --git a/rust/c509-certificate/src/c509.rs b/rust/c509-certificate/src/c509.rs index 75cc23bfa9f..72c3bc90368 100644 --- a/rust/c509-certificate/src/c509.rs +++ b/rust/c509-certificate/src/c509.rs @@ -3,7 +3,13 @@ use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Serialize}; -use crate::tbs_cert::TbsCert; +use crate::{ + cert_tbs::TbsCert, + helper::{ + decode::{decode_bytes, decode_datatype}, + encode::{encode_bytes, encode_null}, + }, +}; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] /// A struct represents the `C509` Certificate. @@ -26,13 +32,13 @@ impl C509 { /// Get the `TBSCertificate` of the C509 Certificate. #[must_use] - pub fn get_tbs_cert(&self) -> &TbsCert { + pub fn tbs_cert(&self) -> &TbsCert { &self.tbs_cert } /// Get the `IssuerSignatureValue` of the C509 Certificate. #[must_use] - pub fn get_issuer_signature_value(&self) -> &Option> { + pub fn issuer_signature_value(&self) -> &Option> { &self.issuer_signature_value } } @@ -43,8 +49,8 @@ impl Encode<()> for C509 { ) -> Result<(), minicbor::encode::Error> { self.tbs_cert.encode(e, ctx)?; match self.issuer_signature_value { - Some(ref value) => e.bytes(value)?, - None => e.null()?, + Some(ref value) => encode_bytes(e, "C509 Issuer Signature value", value)?, + None => encode_null(e, "C509 Issuer Signature value")?, }; Ok(()) } @@ -53,8 +59,8 @@ impl Encode<()> for C509 { impl Decode<'_, ()> for C509 { fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { let tbs_cert = TbsCert::decode(d, ctx)?; - let issuer_signature_value = match d.datatype()? { - minicbor::data::Type::Bytes => Some(d.bytes()?.to_vec()), + let issuer_signature_value = match decode_datatype(d, "C509 Issuer Signature value")? { + minicbor::data::Type::Bytes => Some(decode_bytes(d, "C509 Issuer Signature value")?), _ => None, }; Ok(Self::new(tbs_cert, issuer_signature_value)) diff --git a/rust/c509-certificate/src/cert_tbs.rs b/rust/c509-certificate/src/cert_tbs.rs new file mode 100644 index 00000000000..fb80182ed8a --- /dev/null +++ b/rust/c509-certificate/src/cert_tbs.rs @@ -0,0 +1,525 @@ +//! C509 certificate To Be Sign Certificate (TBS Certificate) + +use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; +use serde::{Deserialize, Serialize}; + +use crate::{ + big_uint::UnwrappedBigUint, + extensions::Extensions, + helper::{ + decode::{decode_bytes, decode_helper}, + encode::{encode_bytes, encode_helper}, + }, + issuer_sig_algo::IssuerSignatureAlgorithm, + name::Name, + subject_pub_key_algo::SubjectPubKeyAlgorithm, + time::Time, +}; + +/// A struct represents a To Be Signed Certificate (TBS Certificate). +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct TbsCert { + /// Certificate type. + c509_certificate_type: u8, + /// Serial number of the certificate. + certificate_serial_number: UnwrappedBigUint, + /// Issuer Signature Algorithm + issuer_signature_algorithm: IssuerSignatureAlgorithm, + /// Issuer + issuer: Name, + /// Validity not before. + validity_not_before: Time, + /// Validity not after. + validity_not_after: Time, + /// Subject + subject: Name, + /// Subject Public Key Algorithm + subject_public_key_algorithm: SubjectPubKeyAlgorithm, + /// Subject Public Key value + subject_public_key: Vec, + /// Extensions + extensions: Extensions, +} + +impl TbsCert { + /// Create a new instance of TBS Certificate. + /// If issuer is not provided, it will use the subject as the issuer. + #[must_use] + #[allow(clippy::too_many_arguments)] + pub fn new( + c509_certificate_type: u8, certificate_serial_number: UnwrappedBigUint, + issuer_signature_algorithm: IssuerSignatureAlgorithm, issuer: Option, + validity_not_before: Time, validity_not_after: Time, subject: Name, + subject_public_key_algorithm: SubjectPubKeyAlgorithm, subject_public_key: Vec, + extensions: Extensions, + ) -> Self { + Self { + c509_certificate_type, + certificate_serial_number, + issuer_signature_algorithm, + issuer: issuer.unwrap_or_else(|| subject.clone()), + validity_not_before, + validity_not_after, + subject, + subject_public_key_algorithm, + subject_public_key, + extensions, + } + } + + /// Get the certificate type. + #[must_use] + pub fn c509_certificate_type(&self) -> u8 { + self.c509_certificate_type + } + + /// Get the certificate serial number. + #[must_use] + pub fn certificate_serial_number(&self) -> &UnwrappedBigUint { + &self.certificate_serial_number + } + + /// Get the issuer signature algorithm. + #[must_use] + pub fn get_issuer_signature_algorithm(&self) -> &IssuerSignatureAlgorithm { + &self.issuer_signature_algorithm + } + + /// Get the issuer. + #[must_use] + pub fn issuer(&self) -> &Name { + &self.issuer + } + + /// Get the validity not before. + #[must_use] + pub fn validity_not_before(&self) -> &Time { + &self.validity_not_before + } + + /// Get the validity not after. + #[must_use] + pub fn validity_not_after(&self) -> &Time { + &self.validity_not_after + } + + /// Get the subject. + #[must_use] + pub fn subject(&self) -> &Name { + &self.subject + } + + /// Get the subject public key algorithm. + #[must_use] + pub fn subject_public_key_algorithm(&self) -> &SubjectPubKeyAlgorithm { + &self.subject_public_key_algorithm + } + + /// Get the subject public key. + #[must_use] + pub fn subject_public_key(&self) -> &[u8] { + &self.subject_public_key + } + + /// Get the extensions. + #[must_use] + pub fn extensions(&self) -> &Extensions { + &self.extensions + } + + /// Get the issuer signature algorithm. + #[must_use] + pub fn issuer_signature_algorithm(&self) -> &IssuerSignatureAlgorithm { + &self.issuer_signature_algorithm + } +} + +impl Encode<()> for TbsCert { + fn encode( + &self, e: &mut Encoder, ctx: &mut (), + ) -> Result<(), minicbor::encode::Error> { + encode_helper(e, "Certificate type", ctx, &self.c509_certificate_type)?; + self.certificate_serial_number.encode(e, ctx)?; + self.issuer_signature_algorithm.encode(e, ctx)?; + self.issuer.encode(e, ctx)?; + self.validity_not_before.encode(e, ctx)?; + self.validity_not_after.encode(e, ctx)?; + self.subject.encode(e, ctx)?; + self.subject_public_key_algorithm.encode(e, ctx)?; + encode_bytes(e, "Subject Public Key", &self.subject_public_key)?; + self.extensions.encode(e, ctx)?; + Ok(()) + } +} + +impl Decode<'_, ()> for TbsCert { + fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { + let cert_type = decode_helper(d, "Certificate type", ctx)?; + let serial_number = UnwrappedBigUint::decode(d, ctx)?; + let issuer_signature_algorithm = IssuerSignatureAlgorithm::decode(d, ctx)?; + let issuer = Some(Name::decode(d, ctx)?); + let not_before = Time::decode(d, ctx)?; + let not_after = Time::decode(d, ctx)?; + let subject = Name::decode(d, ctx)?; + let subject_public_key_algorithm = SubjectPubKeyAlgorithm::decode(d, ctx)?; + let subject_public_key = decode_bytes(d, "Subject Public Key")?; + let extensions = Extensions::decode(d, ctx)?; + + Ok(TbsCert::new( + cert_type, + serial_number, + issuer_signature_algorithm, + issuer, + not_before, + not_after, + subject, + subject_public_key_algorithm, + subject_public_key, + extensions, + )) + } +} + +// ------------------Test---------------------- + +// Notes +// - The test from https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/ +// currently uses `subject_public_key` id-ecPublicKey, which has special encoding and +// decoding that this crate does not yet support. Hence, this test has +// been modified to align with the current encoding and decoding. +// - Currently support natively signed c509 certificate, so all text strings +// are UTF-8 encoded and all attributeType SHALL be non-negative +// - Some Extension values are not supported yet. + +#[cfg(test)] +pub(crate) mod test_tbs_cert { + use asn1_rs::oid; + + use super::*; + use crate::{ + attributes::attribute::{Attribute, AttributeValue}, + extensions::{ + alt_name::{AlternativeName, GeneralNamesOrText}, + extension::{Extension, ExtensionValue}, + }, + general_names::{ + general_name::{GeneralName, GeneralNameTypeRegistry, GeneralNameValue}, + other_name_hw_module::OtherNameHardwareModuleName, + GeneralNames, + }, + name::NameValue, + }; + + // Mnemonic: match mad promote group rival case + const PUBKEY: [u8; 8] = [0x88, 0xD0, 0xB6, 0xB0, 0xB3, 0x7B, 0xAA, 0x46]; + + // Test reference https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/ + // A.1. Example RFC 7925 profiled X.509 Certificate + pub(crate) fn tbs_1() -> (TbsCert, String) { + let tbs_certificate = ( + 3, // c509_certificate_type + 128_269, // certificate_serial_number + oid!(1.2.840 .10045 .4 .3 .2), // issuer_signature_algorithm (ecdsa-with-SHA256) + ( + // issuer + oid!(2.5.4 .3), // oid (commonName) + "RFC test CA", // value + false, // critical + ), + 1_672_531_200, // validity_not_before + 1_767_225_600, // validity_not_after + ( + // subject + oid!(2.5.4 .3), // oid (commonName) + "01-23-45-FF-FE-67-89-AB", // value + false, // critical + ), + oid!(1.2.840 .10045 .2 .1), /* subject_public_key_algorithm (id-ecPublicKey + * prime256v1 P-256) */ + PUBKEY, // subject_public_key (modified from the example) + ( + // extensions + oid!(2.5.29 .15), // oid (keyUsage) + 1, // value + false, // critical + ), + ); + + let tbs_certificate_cbor = [ + "03", // c509_certificate_type + "4301f50d", // certificate_serial_number + "00", // issuer_signature_algorithm + "6b5246432074657374204341", // issuer + "1a63b0cd00", // validity_not_before + "1a6955b900", // validity_not_after + "47010123456789ab", // subject + "01", // subject_public_key_algorithm + "4888d0b6b0b37baa46", // subject_public_key + "01", // extensions + ]; + + // Issuer + let mut attr1 = Attribute::new(tbs_certificate.3 .0); + attr1.add_value(AttributeValue::Text(tbs_certificate.3 .1.to_string())); + let issuer = Name::new(NameValue::Attribute(vec![attr1])); + + // Subject + let mut attr2 = Attribute::new(tbs_certificate.6 .0); + attr2.add_value(AttributeValue::Text(tbs_certificate.6 .1.to_string())); + let subject = Name::new(NameValue::Attribute(vec![attr2])); + + // Extensions + let mut extensions = Extensions::new(); + extensions.add_extension(Extension::new( + tbs_certificate.9 .0, + ExtensionValue::Int(tbs_certificate.9 .1), + tbs_certificate.9 .2, + )); + + let data = TbsCert::new( + tbs_certificate.0, + UnwrappedBigUint::new(tbs_certificate.1), + IssuerSignatureAlgorithm::new(tbs_certificate.2, None), + Some(issuer), + Time::new(tbs_certificate.4), + Time::new(tbs_certificate.5), + subject, + SubjectPubKeyAlgorithm::new(tbs_certificate.7, None), + tbs_certificate.8.to_vec(), + extensions, + ); + + let concatenated: String = tbs_certificate_cbor.concat(); + + (data, concatenated) + } + + #[test] + fn encode_decode_tbs_cert_1() { + let (tbs_cert, tbs_cert_cbor) = tbs_1(); + + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + tbs_cert + .encode(&mut encoder, &mut ()) + .expect("Failed to encode TBS Certificate"); + + assert_eq!(hex::encode(buffer.clone()), tbs_cert_cbor); + + let mut decoder = Decoder::new(&buffer); + let decoded_tbs = + TbsCert::decode(&mut decoder, &mut ()).expect("Failed to decode TBS Certificate"); + assert_eq!(decoded_tbs, tbs_cert); + } + + // Test reference https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/ + // A.2. Example IEEE 802.1AR profiled X.509 Certificate + #[allow(clippy::too_many_lines)] + fn tbs_2() -> (TbsCert, String) { + let tbs_certificate = ( + 3, // c509_certificate_type + 9_112_578_475_118_446_130, // certificate_serial_number + oid!(1.2.840 .10045 .4 .3 .2), // issuer_signature_algorithm (ecdsa-with-SHA256) + [ + // issuer + ( + oid!(2.5.4 .6), // oid (C: countryName) + "US", // value + false, // critical + ), + ( + oid!(2.5.4 .8), // oid (ST: stateOrProvinceName) + "CA", // value + false, // critical + ), + ( + oid!(2.5.4 .10), // oid (O: organizationName) + "Example Inc", // value + false, // critical + ), + ( + oid!(2.5.4 .11), // oid (OU: organizationalUnitName) + "certification", // value + false, // critical + ), + ( + oid!(2.5.4 .3), // oid (CN: commonName) + "802.1AR CA", // value + false, // critical + ), + ], + 1_548_934_156, // validity_not_before + 253_402_300_799, // validity_not_after + [ + // subject + ( + oid!(2.5.4 .6), // oid (C: countryName) + "US", // value + false, // critical + ), + ( + oid!(2.5.4 .8), // oid (ST: stateOrProvinceName) + "CA", // value + false, // critical + ), + ( + oid!(2.5.4 .7), // oid (L: localityName) + "LA", // value + false, // critical + ), + ( + oid!(2.5.4 .10), // oid (O: organizationName) + "example Inc", // value + false, // critical + ), + ( + oid!(2.5.4 .11), // oid (OU: organizationalUnitName) + "IoT", // value + false, // critical + ), + ( + oid!(2.5.4 .5), // oid (serialNumber) + "Wt1234", // value + false, // critical + ), + ], + oid!(1.2.840 .10045 .2 .1), /* subject_public_key_algorithm (id-ecPublicKey + * prime256v1 P-256) */ + PUBKEY, // subject_public_key (modified from the example) + ( + // extensions + ( + oid!(2.5.29 .19), // oid (basicConstraints) + -2, // value + false, // critical + ), + ( + oid!(2.5.29 .14), // oid (subjectKeyIdentifier) + [ + 0x96, 0x60, 0x0D, 0x87, 0x16, 0xBF, 0x7F, 0xD0, 0xE7, 0x52, 0xD0, 0xAC, + 0x76, 0x07, 0x77, 0xAD, 0x66, 0x5D, 0x02, 0xA0, + ], // value + false, // critical + ), + ( + oid!(2.5.29 .15), // oid (keyUsage) + 5, // value + true, // critical + ), + ( + oid!(2.5.29 .17), // oid (subjectAltName) + ( + oid!(1.3.6 .1 .4 .1 .6175 .10 .1), // hwType + [0x01, 0x02, 0x03, 0x04], // hwSerialNum + ), + false, // critical + ), + ), + ); + + let tbs_certificate_cbor = [ + "03", // c509_certificate_type + "487e7661d7b54e4632", // certificate_serial_number + "00", // issuer_signature_algorithm + "8a0462555306624341086b4578616d706c6520496e63096d63657274696669636174696f6e016a3830322e314152204341", // issuer + "1a5c52dc0c", // validity_not_before + "f6", // validity_not_after + "8c046255530662434105624c41086b6578616d706c6520496e630963496f540366577431323334", // subject + "01", // subject_public_key_algorithm + "4888d0b6b0b37baa46", // subject_public_key + "840421015496600d8716bf7fd0e752d0ac760777ad665d02a0210503822082492b06010401b01f0a014401020304", // extensions + ]; + + // Issuer + let mut attributes_1 = Vec::new(); + for i in 0..tbs_certificate.3.len() { + let mut attr = Attribute::new(tbs_certificate.3.get(i).unwrap().0.clone()); + attr.add_value(AttributeValue::Text( + tbs_certificate.3.get(i).unwrap().1.to_string(), + )); + attributes_1.push(attr); + } + let issuer = Name::new(NameValue::Attribute(attributes_1)); + + // Subject + let mut attributes_2 = Vec::new(); + for i in 0..tbs_certificate.6.len() { + let mut attr = Attribute::new(tbs_certificate.6.get(i).unwrap().0.clone()); + attr.add_value(AttributeValue::Text( + tbs_certificate.6.get(i).unwrap().1.to_string(), + )); + attributes_2.push(attr); + } + let subject = Name::new(NameValue::Attribute(attributes_2)); + + // Extensions + let mut extensions = Extensions::new(); + extensions.add_extension(Extension::new( + tbs_certificate.9 .0 .0, + ExtensionValue::Int(tbs_certificate.9 .0 .1), + tbs_certificate.9 .0 .2, + )); + extensions.add_extension(Extension::new( + tbs_certificate.9 .1 .0, + ExtensionValue::Bytes(tbs_certificate.9 .1 .1.to_vec()), + tbs_certificate.9 .1 .2, + )); + extensions.add_extension(Extension::new( + tbs_certificate.9 .2 .0, + ExtensionValue::Int(tbs_certificate.9 .2 .1), + tbs_certificate.9 .2 .2, + )); + let mut gns = GeneralNames::new(); + let hw = OtherNameHardwareModuleName::new( + tbs_certificate.9 .3 .1 .0, + tbs_certificate.9 .3 .1 .1.to_vec(), + ); + gns.add_general_name(GeneralName::new( + GeneralNameTypeRegistry::OtherNameHardwareModuleName, + GeneralNameValue::OtherNameHWModuleName(hw), + )); + + extensions.add_extension(Extension::new( + tbs_certificate.9 .3 .0, + ExtensionValue::AlternativeName(AlternativeName::new( + GeneralNamesOrText::GeneralNames(gns), + )), + false, + )); + + let data = TbsCert::new( + tbs_certificate.0, + UnwrappedBigUint::new(tbs_certificate.1), + IssuerSignatureAlgorithm::new(tbs_certificate.2, None), + Some(issuer), + Time::new(tbs_certificate.4), + Time::new(tbs_certificate.5), + subject, + SubjectPubKeyAlgorithm::new(tbs_certificate.7, None), + tbs_certificate.8.to_vec(), + extensions, + ); + + let concatenated: String = tbs_certificate_cbor.concat(); + + (data, concatenated) + } + + #[test] + fn encode_decode_tbs_cert_2() { + let (tbs_cert, tbs_cert_cbor) = tbs_2(); + + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + tbs_cert + .encode(&mut encoder, &mut ()) + .expect("Failed to encode TBS Certificate"); + assert_eq!(hex::encode(buffer.clone()), tbs_cert_cbor); + + let mut decoder = Decoder::new(&buffer); + let decoded_tbs = + TbsCert::decode(&mut decoder, &mut ()).expect("Failed to decode TBS Certificate"); + assert_eq!(decoded_tbs, tbs_cert); + } +} diff --git a/rust/c509-certificate/src/extensions/alt_name.rs b/rust/c509-certificate/src/extensions/alt_name.rs index f5448ff032d..0f9896c8588 100644 --- a/rust/c509-certificate/src/extensions/alt_name.rs +++ b/rust/c509-certificate/src/extensions/alt_name.rs @@ -4,9 +4,15 @@ use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Serialize}; -use crate::general_names::{ - general_name::{GeneralName, GeneralNameTypeRegistry, GeneralNameValue}, - GeneralNames, +use crate::{ + general_names::{ + general_name::{GeneralName, GeneralNameTypeRegistry, GeneralNameValue}, + GeneralNames, + }, + helper::{ + decode::{decode_datatype, decode_helper}, + encode::encode_helper, + }, }; /// Alternative Name extension. @@ -21,9 +27,9 @@ impl AlternativeName { Self(value) } - /// Get the inner of Alternative Name. + /// Get the general name which can be general names or text. #[must_use] - pub fn get_inner(&self) -> &GeneralNamesOrText { + pub fn general_name(&self) -> &GeneralNamesOrText { &self.0 } } @@ -45,6 +51,8 @@ impl Decode<'_, ()> for AlternativeName { // ------------------GeneralNamesOrText-------------------- /// Enum for type that can be a `GeneralNames` or a text use in `AlternativeName`. +/// Type `Text` is also considered as a `GeneralNames` with only 1 `DNSName` as +/// a special case. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum GeneralNamesOrText { @@ -61,18 +69,18 @@ impl Encode<()> for GeneralNamesOrText { match self { GeneralNamesOrText::GeneralNames(gns) => { let gn = gns - .get_inner() + .general_names() .first() - .ok_or(minicbor::encode::Error::message("GeneralNames is empty"))?; + .ok_or(minicbor::encode::Error::message("General Names is empty"))?; // Check whether there is only 1 item in the array which is a DNSName - if gns.get_inner().len() == 1 && gn.get_gn_type().is_dns_name() { - gn.get_gn_value().encode(e, ctx)?; + if gns.general_names().len() == 1 && gn.gn_type().is_dns_name() { + gn.gn_value().encode(e, ctx)?; } else { gns.encode(e, ctx)?; } }, GeneralNamesOrText::Text(text) => { - e.str(text)?; + encode_helper(e, "Alternative Name - General Name Text", ctx, text)?; }, } Ok(()) @@ -81,15 +89,19 @@ impl Encode<()> for GeneralNamesOrText { impl Decode<'_, ()> for GeneralNamesOrText { fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { - match d.datatype()? { + match decode_datatype(d, "Alternative Name - General Names")? { // If it is a string it is a GeneralNames with only 1 DNSName minicbor::data::Type::String => { let gn_dns = GeneralName::new( GeneralNameTypeRegistry::DNSName, - GeneralNameValue::Text(d.str()?.to_string()), + GeneralNameValue::Text(decode_helper( + d, + "Alternative Name - General Name Text", + ctx, + )?), ); let mut gns = GeneralNames::new(); - gns.add_gn(gn_dns); + gns.add_general_name(gn_dns); Ok(GeneralNamesOrText::GeneralNames(gns)) }, minicbor::data::Type::Array => { @@ -120,7 +132,7 @@ mod test_alt_name { let mut buffer = Vec::new(); let mut encoder = Encoder::new(&mut buffer); let mut gns = GeneralNames::new(); - gns.add_gn(GeneralName::new( + gns.add_general_name(GeneralName::new( GeneralNameTypeRegistry::DNSName, GeneralNameValue::Text("example.com".to_string()), )); @@ -151,7 +163,7 @@ mod test_alt_name { // If only text, it should be GeneralNames with only 1 DNSName let mut gns = GeneralNames::new(); - gns.add_gn(GeneralName::new( + gns.add_general_name(GeneralName::new( GeneralNameTypeRegistry::DNSName, GeneralNameValue::Text("example.com".to_string()), )); diff --git a/rust/c509-certificate/src/extensions/extension/data.rs b/rust/c509-certificate/src/extensions/extension/data.rs index 9bf2b5a8fc8..09980dd4448 100644 --- a/rust/c509-certificate/src/extensions/extension/data.rs +++ b/rust/c509-certificate/src/extensions/extension/data.rs @@ -1,5 +1,5 @@ //! Extension data provides a necessary information for encoding and decoding of C509 -//! Extension. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! Extension. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) //! Section 9.4 C509 Extensions Registry for more information. // cspell: words Evt diff --git a/rust/c509-certificate/src/extensions/extension/mod.rs b/rust/c509-certificate/src/extensions/extension/mod.rs index b2b82e54926..8fc4ea21cfc 100644 --- a/rust/c509-certificate/src/extensions/extension/mod.rs +++ b/rust/c509-certificate/src/extensions/extension/mod.rs @@ -10,7 +10,13 @@ use serde::{Deserialize, Deserializer, Serialize}; use strum_macros::EnumDiscriminants; use super::alt_name::AlternativeName; -use crate::oid::{C509oid, C509oidRegistered}; +use crate::{ + helper::{ + decode::{decode_bytes, decode_datatype, decode_helper}, + encode::{encode_bytes, encode_helper}, + }, + oid::{C509oid, C509oidRegistered}, +}; /// A struct of C509 `Extension` #[derive(Debug, Clone, PartialEq)] @@ -29,8 +35,7 @@ impl Extension { #[must_use] pub fn new(oid: Oid<'static>, value: ExtensionValue, critical: bool) -> Self { Self { - registered_oid: C509oidRegistered::new(oid, EXTENSIONS_LOOKUP.get_int_to_oid_table()) - .pen_encoded(), + registered_oid: C509oidRegistered::new(oid, EXTENSIONS_LOOKUP.get_int_to_oid_table()), critical, value, } @@ -38,19 +43,19 @@ impl Extension { /// Get the value of the `Extension` in `ExtensionValue`. #[must_use] - pub fn get_value(&self) -> &ExtensionValue { + pub fn value(&self) -> &ExtensionValue { &self.value } /// Get the critical flag of the `Extension`. #[must_use] - pub fn get_critical(&self) -> bool { + pub fn critical(&self) -> bool { self.critical } /// Get the registered OID of the `Extension`. #[must_use] - pub fn get_registered_oid(&self) -> &C509oidRegistered { + pub(crate) fn registered_oid(&self) -> &C509oidRegistered { &self.registered_oid } } @@ -81,7 +86,7 @@ impl Serialize for Extension { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { let helper = Helper { - oid: self.registered_oid.get_c509_oid().get_oid().to_string(), + oid: self.registered_oid.c509_oid().oid().to_string(), value: self.value.clone(), critical: self.critical, }; @@ -93,16 +98,15 @@ impl Encode<()> for Extension { // Extension can be encoded as: // - (extensionID: int, extensionValue: any) // - (extensionID: ~oid, ? critical: true, extensionValue: bytes) - // - (extensionID: pen, ? critical: true, extensionValue: bytes) fn encode( &self, e: &mut Encoder, ctx: &mut (), ) -> Result<(), minicbor::encode::Error> { // Handle CBOR int based on OID mapping if let Some(&mapped_oid) = self .registered_oid - .get_table() + .table() .get_map() - .get_by_right(&self.registered_oid.get_c509_oid().get_oid()) + .get_by_right(self.registered_oid.c509_oid().oid()) { // Determine encoded OID value based on critical flag let encoded_oid = if self.critical { @@ -110,12 +114,12 @@ impl Encode<()> for Extension { } else { mapped_oid }; - e.i16(encoded_oid)?; + encode_helper(e, "Extension as OID int", ctx, &encoded_oid)?; } else { - // Handle unwrapped CBOR OID or CBOR PEN - self.registered_oid.get_c509_oid().encode(e, ctx)?; + // Handle unwrapped CBOR OID + self.registered_oid.c509_oid().encode(e, ctx)?; if self.critical { - e.bool(self.critical)?; + encode_helper(e, "Extension critical", ctx, &self.critical)?; } } // Encode the extension value @@ -126,7 +130,7 @@ impl Encode<()> for Extension { impl Decode<'_, ()> for Extension { fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { - match d.datatype()? { + match decode_datatype(d, "Extension")? { // Check whether OID is an int // Even the encoding is i16, the minicbor decoder doesn't know what type we encoded, // so need to check every possible type. @@ -134,7 +138,7 @@ impl Decode<'_, ()> for Extension { | minicbor::data::Type::U16 | minicbor::data::Type::I8 | minicbor::data::Type::I16 => { - let int_value = d.i16()?; + let int_value: i16 = decode_helper(d, "Extension as OID int", ctx)?; // OID can be negative due to critical flag, so need absolute the value let abs_int_value = int_value.abs(); let oid = @@ -151,20 +155,21 @@ impl Decode<'_, ()> for Extension { )) }, _ => { - // Handle unwrapped CBOR OID or CBOR PEN + // Handle unwrapped CBOR OID let c509_oid = C509oid::decode(d, ctx)?; // Critical flag is optional, so if exist, this mean we have to decode it - let critical = if d.datatype()? == minicbor::data::Type::Bool { - d.bool()? - } else { - false - }; + let critical = + if decode_datatype(d, "Extension critical")? == minicbor::data::Type::Bool { + decode_helper(d, "Extension critical", ctx)? + } else { + false + }; // Decode bytes for extension value - let extension_value = ExtensionValue::Bytes(d.bytes()?.to_vec()); + let extension_value = ExtensionValue::Bytes(decode_bytes(d, "Extension")?); Ok(Extension::new( - c509_oid.get_oid(), + c509_oid.oid().clone(), extension_value, critical, )) @@ -209,10 +214,10 @@ impl Encode<()> for ExtensionValue { ) -> Result<(), minicbor::encode::Error> { match self { ExtensionValue::Int(value) => { - e.i64(*value)?; + encode_helper(e, "Extension Value", ctx, value)?; }, ExtensionValue::Bytes(value) => { - e.bytes(value)?; + encode_bytes(e, "Extension value", value)?; }, ExtensionValue::AlternativeName(value) => { value.encode(e, ctx)?; @@ -233,11 +238,11 @@ where C: ExtensionValueTypeTrait + Debug fn decode(d: &mut Decoder<'_>, ctx: &mut C) -> Result { match ctx.get_type() { ExtensionValueType::Int => { - let value = d.i64()?; + let value = decode_helper(d, "Extension value", ctx)?; Ok(ExtensionValue::Int(value)) }, ExtensionValueType::Bytes => { - let value = d.bytes()?.to_vec(); + let value = decode_bytes(d, "Extension value")?; Ok(ExtensionValue::Bytes(value)) }, ExtensionValueType::AlternativeName => { @@ -302,7 +307,7 @@ mod test_extension { let mut buffer = Vec::new(); let mut encoder = Encoder::new(&mut buffer); - // Not PEN OID and not in the registry table + // Not in the registry table // Value should be bytes let ext = Extension::new( oid!(2.16.840 .1 .101 .3 .4 .2 .1), diff --git a/rust/c509-certificate/src/extensions/mod.rs b/rust/c509-certificate/src/extensions/mod.rs index 1288f9e642b..a93eff2ca02 100644 --- a/rust/c509-certificate/src/extensions/mod.rs +++ b/rust/c509-certificate/src/extensions/mod.rs @@ -1,21 +1,18 @@ -//! C509 Extension as a part of `TBSCertificate` used in C509 Certificate. +//! C509 Extensions //! //! Extension fallback of C509 OID extension -//! Given OID if not found in the registered OID table, it will be encoded as a PEN OID. -//! If the OID is not a PEN OID, it will be encoded as an unwrapped OID. +//! Given OID, if it is found in the registered OID table, int value of the +//! associated OID will be used, if not, it will be encoded as an unwrapped OID (~oid). //! //! ```cddl -//! Extensions and Extension can be encoded as the following: //! Extensions = [ * Extension ] / int //! Extension = ( extensionID: int, extensionValue: any ) // -//! ( extensionID: ~oid, ? critical: true, -//! extensionValue: bytes ) // -//! ( extensionID: pen, ? critical: true, -//! extensionValue: bytes ) +//! ( extensionID: ~oid, ? critical: true, +//! extensionValue: bytes ) // //! ``` //! //! For more information about Extensions, -//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) pub mod alt_name; pub mod extension; @@ -27,6 +24,10 @@ use extension::{Extension, ExtensionValue}; use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Serialize}; +use crate::helper::{ + decode::{decode_array_len, decode_datatype, decode_helper}, + encode::{encode_array_len, encode_helper}, +}; /// OID of `KeyUsage` extension static KEY_USAGE_OID: Oid<'static> = oid!(2.5.29 .15); @@ -34,12 +35,6 @@ static KEY_USAGE_OID: Oid<'static> = oid!(2.5.29 .15); #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Extensions(Vec); -impl Default for Extensions { - fn default() -> Self { - Self::new() - } -} - impl Extensions { /// Create a new instance of `Extensions` as empty vector. #[must_use] @@ -47,15 +42,21 @@ impl Extensions { Self(Vec::new()) } + /// Get the inner vector of `Extensions`. + #[must_use] + pub fn extensions(&self) -> &[Extension] { + &self.0 + } + /// Add an `Extension` to the `Extensions`. - pub fn add_ext(&mut self, extension: Extension) { + pub fn add_extension(&mut self, extension: Extension) { self.0.push(extension); } +} - /// Get the inner vector of `Extensions`. - #[must_use] - pub fn get_inner(&self) -> &Vec { - &self.0 +impl Default for Extensions { + fn default() -> Self { + Self::new() } } @@ -66,17 +67,11 @@ impl Encode<()> for Extensions { // If there is only one extension and it is KeyUsage, encode as int // encoding as absolute value of the second int and the sign of the first int if let Some(extension) = self.0.first() { - if self.0.len() == 1 - && extension.get_registered_oid().get_c509_oid().get_oid() == KEY_USAGE_OID - { - match extension.get_value() { + if self.0.len() == 1 && extension.registered_oid().c509_oid().oid() == &KEY_USAGE_OID { + match extension.value() { ExtensionValue::Int(value) => { - let ku_value = if extension.get_critical() { - -value - } else { - *value - }; - e.i64(ku_value)?; + let ku_value = if extension.critical() { -value } else { *value }; + encode_helper(e, "Extensions KeyUsage", ctx, &ku_value)?; return Ok(()); }, _ => { @@ -88,7 +83,7 @@ impl Encode<()> for Extensions { } } // Else handle the array of `Extension` - e.array(self.0.len() as u64)?; + encode_array_len(e, "Extensions", self.0.len() as u64)?; for extension in &self.0 { extension.encode(e, ctx)?; } @@ -99,16 +94,18 @@ impl Encode<()> for Extensions { impl Decode<'_, ()> for Extensions { fn decode(d: &mut Decoder<'_>, _ctx: &mut ()) -> Result { // If only KeyUsage is in the extension -> will only contain an int - if d.datatype()? == minicbor::data::Type::U8 || d.datatype()? == minicbor::data::Type::I8 { + if decode_datatype(d, "Extensions KeyUsage")? == minicbor::data::Type::U8 + || decode_datatype(d, "Extensions KeyUsage")? == minicbor::data::Type::I8 + { // Check if it's a negative number (critical extension) - let critical = d.datatype()? == minicbor::data::Type::I8; + let critical = + decode_datatype(d, "Extensions KeyUsage critical")? == minicbor::data::Type::I8; // Note that 'KeyUsage' BIT STRING is interpreted as an unsigned integer, // so we can absolute the value - let value = d.i64()?.abs(); - - let extension_value = ExtensionValue::Int(value); + let value: i64 = decode_helper(d, "Extensions KeyUsage value", &mut ())?; + let extension_value = ExtensionValue::Int(value.abs()); let mut extensions = Extensions::new(); - extensions.add_ext(Extension::new( + extensions.add_extension(Extension::new( KEY_USAGE_OID.clone(), extension_value, critical, @@ -116,13 +113,11 @@ impl Decode<'_, ()> for Extensions { return Ok(extensions); } // Handle array of extensions - let len = d - .array()? - .ok_or_else(|| minicbor::decode::Error::message("Failed to get array length"))?; + let len = decode_array_len(d, "Extensions")?; let mut extensions = Extensions::new(); for _ in 0..len { let extension = Extension::decode(d, &mut ())?; - extensions.add_ext(extension); + extensions.add_extension(extension); } Ok(extensions) @@ -141,7 +136,7 @@ mod test_extensions { let mut encoder = Encoder::new(&mut buffer); let mut exts = Extensions::new(); - exts.add_ext(Extension::new( + exts.add_extension(Extension::new( oid!(2.5.29 .15), ExtensionValue::Int(2), false, @@ -164,7 +159,7 @@ mod test_extensions { let mut encoder = Encoder::new(&mut buffer); let mut exts = Extensions::new(); - exts.add_ext(Extension::new( + exts.add_extension(Extension::new( oid!(2.5.29 .15), ExtensionValue::Int(2), true, @@ -187,13 +182,13 @@ mod test_extensions { let mut encoder = Encoder::new(&mut buffer); let mut exts = Extensions::new(); - exts.add_ext(Extension::new( + exts.add_extension(Extension::new( oid!(2.5.29 .15), ExtensionValue::Int(2), false, )); - exts.add_ext(Extension::new( + exts.add_extension(Extension::new( oid!(2.5.29 .14), ExtensionValue::Bytes([1, 2, 3, 4].to_vec()), false, diff --git a/rust/c509-certificate/src/general_names/data.rs b/rust/c509-certificate/src/general_names/data.rs index 71ff7649492..993d031ecf2 100644 --- a/rust/c509-certificate/src/general_names/data.rs +++ b/rust/c509-certificate/src/general_names/data.rs @@ -1,5 +1,5 @@ //! General Name data provides a necessary information for encoding and decoding of C509 -//! General Name. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! General Name. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) //! Section 9.9 C509 General Names Registry for more information. // cspell: words Gntr Gnvt diff --git a/rust/c509-certificate/src/general_names/general_name.rs b/rust/c509-certificate/src/general_names/general_name.rs index 0549f37bb4c..9cce5cc59df 100644 --- a/rust/c509-certificate/src/general_names/general_name.rs +++ b/rust/c509-certificate/src/general_names/general_name.rs @@ -1,7 +1,11 @@ //! C509 General Name //! +//! ```cddl +//! GeneralName = ( GeneralNameType : int, GeneralNameValue : any ) +//! ``` +//! //! For more information about `GeneralName`, -//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) use std::fmt::Debug; @@ -13,7 +17,14 @@ use super::{ data::{get_gn_from_int, get_gn_value_type_from_int, get_int_from_gn}, other_name_hw_module::OtherNameHardwareModuleName, }; -use crate::{name::Name, oid::C509oid}; +use crate::{ + helper::{ + decode::{decode_bytes, decode_datatype, decode_helper}, + encode::{encode_bytes, encode_helper}, + }, + name::Name, + oid::C509oid, +}; /// A struct represents a `GeneralName`. /// ```cddl @@ -37,13 +48,13 @@ impl GeneralName { /// Get the `GeneralName` type. #[must_use] - pub fn get_gn_type(&self) -> &GeneralNameTypeRegistry { + pub fn gn_type(&self) -> &GeneralNameTypeRegistry { &self.gn_type } /// Get the value of the `GeneralName` in `GeneralNameValue`. #[must_use] - pub fn get_gn_value(&self) -> &GeneralNameValue { + pub fn gn_value(&self) -> &GeneralNameValue { &self.value } } @@ -54,7 +65,7 @@ impl Encode<()> for GeneralName { ) -> Result<(), minicbor::encode::Error> { // Encode GeneralNameType as int let i = get_int_from_gn(self.gn_type).map_err(minicbor::encode::Error::message)?; - e.i16(i)?; + encode_helper(e, "General Name as OID int", ctx, &i)?; // Encode GeneralNameValue as its type self.value.encode(e, ctx)?; Ok(()) @@ -63,8 +74,10 @@ impl Encode<()> for GeneralName { impl Decode<'_, ()> for GeneralName { fn decode(d: &mut Decoder<'_>, _ctx: &mut ()) -> Result { - if minicbor::data::Type::U8 == d.datatype()? || minicbor::data::Type::I8 == d.datatype()? { - let i = d.i16()?; + if decode_datatype(d, "General Name as OID int")? == minicbor::data::Type::U8 + || decode_datatype(d, "General Name as OID int")? == minicbor::data::Type::I8 + { + let i = decode_helper(d, "General Name as OID int", &mut ())?; let gn = get_gn_from_int(i).map_err(minicbor::decode::Error::message)?; let value_type = get_gn_value_type_from_int(i).map_err(minicbor::decode::Error::message)?; @@ -75,7 +88,7 @@ impl Decode<'_, ()> for GeneralName { } else { // GeneralName is not type int Err(minicbor::decode::Error::message( - "GeneralName id type invalid, expected int", + "GeneralName ID type invalid, expected int", )) } } @@ -149,10 +162,10 @@ impl Encode<()> for GeneralNameValue { ) -> Result<(), minicbor::encode::Error> { match self { GeneralNameValue::Text(value) => { - e.str(value)?; + encode_helper(e, "General Name value", ctx, value)?; }, GeneralNameValue::Bytes(value) => { - e.bytes(value)?; + encode_bytes(e, "General Name value", value)?; }, GeneralNameValue::Oid(value) => { value.encode(e, ctx)?; @@ -178,11 +191,11 @@ where C: GeneralNameValueTrait + Debug fn decode(d: &mut Decoder<'_>, ctx: &mut C) -> Result { match ctx.get_type() { GeneralNameValueType::Text => { - let value = d.str()?.to_string(); + let value = decode_helper(d, "General Name value", ctx)?; Ok(GeneralNameValue::Text(value)) }, GeneralNameValueType::Bytes => { - let value = d.bytes()?.to_vec(); + let value = decode_bytes(d, "General Name value")?; Ok(GeneralNameValue::Bytes(value)) }, GeneralNameValueType::Oid => { diff --git a/rust/c509-certificate/src/general_names/mod.rs b/rust/c509-certificate/src/general_names/mod.rs index 1998733be30..86f6e3488a4 100644 --- a/rust/c509-certificate/src/general_names/mod.rs +++ b/rust/c509-certificate/src/general_names/mod.rs @@ -1,7 +1,11 @@ //! C509 General Names //! +//! ```cddl +//! GeneralNames = [ + GeneralName ] +//! ``` +//! //! For more information about `GeneralNames`, -//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) mod data; pub mod general_name; @@ -10,6 +14,8 @@ use general_name::GeneralName; use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Serialize}; +use crate::helper::{decode::decode_array_len, encode::encode_array_len}; + /// A struct represents an array of `GeneralName`. /// /// ```cddl @@ -18,12 +24,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct GeneralNames(Vec); -impl Default for GeneralNames { - fn default() -> Self { - Self::new() - } -} - impl GeneralNames { /// Create a new instance of `GeneralNames` as empty vector. #[must_use] @@ -31,15 +31,21 @@ impl GeneralNames { Self(Vec::new()) } + /// Get the inner of `GeneralName`. + #[must_use] + pub fn general_names(&self) -> &[GeneralName] { + &self.0 + } + /// Add a new `GeneralName` to the `GeneralNames`. - pub fn add_gn(&mut self, gn: GeneralName) { + pub fn add_general_name(&mut self, gn: GeneralName) { self.0.push(gn); } +} - /// Get the inner of `GeneralName`. - #[must_use] - pub fn get_inner(&self) -> &Vec { - &self.0 +impl Default for GeneralNames { + fn default() -> Self { + Self::new() } } @@ -49,11 +55,11 @@ impl Encode<()> for GeneralNames { ) -> Result<(), minicbor::encode::Error> { if self.0.is_empty() { return Err(minicbor::encode::Error::message( - "GeneralNames should not be empty", + "General Names should not be empty", )); } // The general name type should be included in array too - e.array(self.0.len() as u64 * 2)?; + encode_array_len(e, "General Names", self.0.len() as u64 * 2)?; for gn in &self.0 { gn.encode(e, ctx)?; } @@ -63,12 +69,10 @@ impl Encode<()> for GeneralNames { impl Decode<'_, ()> for GeneralNames { fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { - let len = d.array()?.ok_or(minicbor::decode::Error::message( - "GeneralNames should be an array", - ))?; + let len = decode_array_len(d, "General Names")?; let mut gn = GeneralNames::new(); for _ in 0..len / 2 { - gn.add_gn(GeneralName::decode(d, ctx)?); + gn.add_general_name(GeneralName::decode(d, ctx)?); } Ok(gn) } @@ -94,28 +98,38 @@ mod test_general_names { let mut encoder = Encoder::new(&mut buffer); let mut gns = GeneralNames::new(); - gns.add_gn(GeneralName::new( + gns.add_general_name(GeneralName::new( GeneralNameTypeRegistry::DNSName, GeneralNameValue::Text("example.com".to_string()), )); - gns.add_gn(GeneralName::new( + gns.add_general_name(GeneralName::new( GeneralNameTypeRegistry::OtherNameHardwareModuleName, GeneralNameValue::OtherNameHWModuleName(OtherNameHardwareModuleName::new( oid!(2.16.840 .1 .101 .3 .4 .2 .1), vec![0x01, 0x02, 0x03, 0x04], )), )); - gns.add_gn(GeneralName::new( + gns.add_general_name(GeneralName::new( GeneralNameTypeRegistry::IPAddress, GeneralNameValue::Bytes(Ipv4Addr::new(192, 168, 1, 1).octets().to_vec()), )); - gns.add_gn(GeneralName::new( + gns.add_general_name(GeneralName::new( GeneralNameTypeRegistry::RegisteredID, GeneralNameValue::Oid(C509oid::new(oid!(2.16.840 .1 .101 .3 .4 .2 .1))), )); gns.encode(&mut encoder, &mut ()) .expect("Failed to encode GeneralNames"); // Array of 4 GeneralName (type, value) so 8 items: 0x88 + // Unsigned int 2 for DNSName: 0x02 + // DNSName with "example.com": 0x6b6578616d706c652e636f6d + // OtherNameHardwareModuleName negative 1: 0x20 + // Array of 2 items: 0x82 + // OID 2.16.840 .1 .101 .3 .4 .2 .1: 0x49608648016503040201 + // vec![0x01, 0x02, 0x03, 0x04]: 0x4401020304 + // IPAddress: 0x07 + // IPAddress Value in bytes string 192, 168, 1, 1: 0x44c0a80101 + // RegisteredID: 0x08 + // OID 2.16.840 .1 .101 .3 .4 .2 .1: 0x49608648016503040201 assert_eq!(hex::encode(buffer.clone()), "88026b6578616d706c652e636f6d20824960864801650304020144010203040744c0a801010849608648016503040201"); let mut decoder = Decoder::new(&buffer); @@ -130,15 +144,15 @@ mod test_general_names { let mut encoder = Encoder::new(&mut buffer); let mut gns = GeneralNames::new(); - gns.add_gn(GeneralName::new( + gns.add_general_name(GeneralName::new( GeneralNameTypeRegistry::DNSName, GeneralNameValue::Text("example.com".to_string()), )); - gns.add_gn(GeneralName::new( + gns.add_general_name(GeneralName::new( GeneralNameTypeRegistry::DNSName, GeneralNameValue::Text("example.com".to_string()), )); - gns.add_gn(GeneralName::new( + gns.add_general_name(GeneralName::new( GeneralNameTypeRegistry::DNSName, GeneralNameValue::Text("example.com".to_string()), )); diff --git a/rust/c509-certificate/src/general_names/other_name_hw_module.rs b/rust/c509-certificate/src/general_names/other_name_hw_module.rs index b57697ce79a..7e2660cc088 100644 --- a/rust/c509-certificate/src/general_names/other_name_hw_module.rs +++ b/rust/c509-certificate/src/general_names/other_name_hw_module.rs @@ -7,7 +7,13 @@ use asn1_rs::Oid; use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Serialize}; -use crate::oid::C509oid; +use crate::{ + helper::{ + decode::{decode_array_len, decode_bytes}, + encode::{encode_array_len, encode_bytes}, + }, + oid::C509oid, +}; /// A struct represents the hardwareModuleName type of otherName. /// Containing a pair of ( hwType, hwSerialNum ) as mentioned in @@ -29,26 +35,42 @@ impl OtherNameHardwareModuleName { hw_serial_num, } } + + /// Get the c509 OID hardware type. + #[must_use] + pub fn hw_type(&self) -> &C509oid { + &self.hw_type + } + + /// Get the hardware serial number. + #[must_use] + pub fn hw_serial_num(&self) -> &[u8] { + &self.hw_serial_num + } } impl Encode<()> for OtherNameHardwareModuleName { fn encode( &self, e: &mut Encoder, ctx: &mut (), ) -> Result<(), minicbor::encode::Error> { - e.array(2)?; + encode_array_len(e, "OtherNameHardwareModule", 2)?; self.hw_type.encode(e, ctx)?; - e.bytes(&self.hw_serial_num)?; + encode_bytes( + e, + "OtherNameHardwareModule serial number", + &self.hw_serial_num, + )?; Ok(()) } } impl<'a> Decode<'a, ()> for OtherNameHardwareModuleName { fn decode(d: &mut Decoder<'a>, ctx: &mut ()) -> Result { - d.array()?; + decode_array_len(d, "OtherNameHardwareModule")?; let hw_type = C509oid::decode(d, ctx)?; - let hw_serial_num = d.bytes()?.to_vec(); + let hw_serial_num = decode_bytes(d, "OtherNameHardwareModule serial number")?; Ok(OtherNameHardwareModuleName::new( - hw_type.get_oid(), + hw_type.oid().clone(), hw_serial_num, )) } diff --git a/rust/c509-certificate/src/helper/decode.rs b/rust/c509-certificate/src/helper/decode.rs new file mode 100644 index 00000000000..d43ef62e5e7 --- /dev/null +++ b/rust/c509-certificate/src/helper/decode.rs @@ -0,0 +1,62 @@ +//! Helper functions for decoding CBOR data. + +use minicbor::{decode, Decoder}; + +/// Generic helper function for decoding different types. +pub(crate) fn decode_helper<'a, T, C>( + d: &mut Decoder<'a>, from: &str, context: &mut C, +) -> Result +where T: minicbor::Decode<'a, C> { + T::decode(d, context).map_err(|e| { + decode::Error::message(format!( + "Failed to decode {:?} in {from}: {e}", + std::any::type_name::() + )) + }) +} + +/// Helper function for decoding bytes. +pub(crate) fn decode_bytes(d: &mut Decoder, from: &str) -> Result, decode::Error> { + d.bytes().map(<[u8]>::to_vec).map_err(|e| { + decode::Error::message(format!( + "Failed to decode bytes in {from}: + {e}" + )) + }) +} + +/// Helper function for decoding array. +pub(crate) fn decode_array_len(d: &mut Decoder, from: &str) -> Result { + d.array() + .map_err(|e| { + decode::Error::message(format!( + "Failed to decode array in {from}: + {e}" + )) + })? + .ok_or(decode::Error::message(format!( + "Failed to decode array in {from}, unexpected indefinite length", + ))) +} + +/// Helper function for decoding null. +pub(crate) fn decode_null(d: &mut Decoder, from: &str) -> Result<(), decode::Error> { + d.null().map_err(|e| { + decode::Error::message(format!( + "Failed to decode null in {from}: + {e}" + )) + }) +} + +/// Helper function for decoding datatype. +pub(crate) fn decode_datatype( + d: &mut Decoder, from: &str, +) -> Result { + d.datatype().map_err(|e| { + decode::Error::message(format!( + "Failed to decode datatype in {from}: + {e}" + )) + }) +} diff --git a/rust/c509-certificate/src/helper/encode.rs b/rust/c509-certificate/src/helper/encode.rs new file mode 100644 index 00000000000..3184ce0a4cd --- /dev/null +++ b/rust/c509-certificate/src/helper/encode.rs @@ -0,0 +1,54 @@ +//! Helper functions for encoding CBOR data. + +use minicbor::{ + encode::{self, Write}, + Encoder, +}; + +/// Generic helper function for encoding different types. +pub(crate) fn encode_helper( + e: &mut Encoder, from: &str, ctx: &mut C, value: &T, +) -> Result<(), encode::Error> +where T: minicbor::Encode { + T::encode(value, e, ctx).map_err(|err| { + encode::Error::with_message( + err, + format!( + "Failed to encode {:?} in {from}", + std::any::type_name::() + ), + ) + })?; + + Ok(()) +} + +/// Helper function for encoding bytes. +pub(crate) fn encode_bytes( + e: &mut Encoder, from: &str, value: &[u8], +) -> Result<(), encode::Error> { + e.bytes(value).map_err(|err| { + encode::Error::with_message(err, format!("Failed to encode bytes in {from}")) + })?; + Ok(()) +} + +/// Helper function for encoding null. +pub(crate) fn encode_null( + e: &mut Encoder, from: &str, +) -> Result<(), encode::Error> { + e.null().map_err(|err| { + encode::Error::with_message(err, format!("Failed to encode null in {from}")) + })?; + Ok(()) +} + +/// Helper function for encoding array. +pub(crate) fn encode_array_len( + e: &mut Encoder, from: &str, len: u64, +) -> Result<(), encode::Error> { + e.array(len).map_err(|err| { + encode::Error::with_message(err, format!("Failed to encode array in {from}")) + })?; + Ok(()) +} diff --git a/rust/c509-certificate/src/helper/mod.rs b/rust/c509-certificate/src/helper/mod.rs new file mode 100644 index 00000000000..fc80e075b4e --- /dev/null +++ b/rust/c509-certificate/src/helper/mod.rs @@ -0,0 +1,3 @@ +//! Helper module +pub mod decode; +pub mod encode; diff --git a/rust/c509-certificate/src/issuer_sig_algo/data.rs b/rust/c509-certificate/src/issuer_sig_algo/data.rs index 1d0e77bf392..c052c7e438b 100644 --- a/rust/c509-certificate/src/issuer_sig_algo/data.rs +++ b/rust/c509-certificate/src/issuer_sig_algo/data.rs @@ -1,5 +1,5 @@ //! Signature algorithm data provides a necessary information for encoding and decoding of -//! C509 `issuerSignatureAlgorithm`. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! C509 `issuerSignatureAlgorithm`. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) //! Section 9.10 C509 Signature Algorithms Registry for more information. // cspell: words RSASSA XMSS diff --git a/rust/c509-certificate/src/issuer_sig_algo/mod.rs b/rust/c509-certificate/src/issuer_sig_algo/mod.rs index ff63acae59a..3fb049d2fae 100644 --- a/rust/c509-certificate/src/issuer_sig_algo/mod.rs +++ b/rust/c509-certificate/src/issuer_sig_algo/mod.rs @@ -1,9 +1,12 @@ -//! C509 Issuer Signature Algorithm as a part of `TBSCertificate` used in C509 +//! C509 Issuer Signature Algorithm //! Certificate. //! //! ```cddl //! issuerSignatureAlgorithm: AlgorithmIdentifier //! ``` +//! +//! For more information about `issuerSignatureAlgorithm`, +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) mod data; @@ -14,7 +17,14 @@ use data::{get_oid_from_int, ISSUER_SIG_ALGO_LOOKUP}; use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Deserializer, Serialize}; -use crate::{algorithm_identifier::AlgorithmIdentifier, oid::C509oidRegistered}; +use crate::{ + algorithm_identifier::AlgorithmIdentifier, + helper::{ + decode::{decode_datatype, decode_helper}, + encode::encode_helper, + }, + oid::C509oidRegistered, +}; /// A struct represents the `IssuerSignatureAlgorithm` #[derive(Debug, Clone, PartialEq)] @@ -37,7 +47,20 @@ impl IssuerSignatureAlgorithm { algo_identifier: AlgorithmIdentifier::new(oid, param), } } + + /// Get the algorithm identifier. + #[must_use] + pub fn algo_identifier(&self) -> &AlgorithmIdentifier { + &self.algo_identifier + } + + /// Get the registered OID. + #[allow(dead_code)] + pub(crate) fn registered_oid(&self) -> &C509oidRegistered { + &self.registered_oid + } } + /// Helper struct for deserialize and serialize `IssuerSignatureAlgorithm`. #[derive(Debug, Deserialize, Serialize)] struct Helper { @@ -62,8 +85,8 @@ impl Serialize for IssuerSignatureAlgorithm { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { let helper = Helper { - oid: self.registered_oid.get_c509_oid().get_oid().to_string(), - param: self.algo_identifier.get_param().clone(), + oid: self.registered_oid.c509_oid().oid().to_string(), + param: self.algo_identifier.param().clone(), }; helper.serialize(serializer) } @@ -75,11 +98,11 @@ impl Encode<()> for IssuerSignatureAlgorithm { ) -> Result<(), minicbor::encode::Error> { if let Some(&i) = self .registered_oid - .get_table() + .table() .get_map() - .get_by_right(&self.registered_oid.get_c509_oid().get_oid()) + .get_by_right(self.registered_oid.c509_oid().oid()) { - e.i16(i)?; + encode_helper(e, "Issuer Signature Algorithm as OID int", ctx, &i)?; } else { AlgorithmIdentifier::encode(&self.algo_identifier, e, ctx)?; } @@ -89,18 +112,18 @@ impl Encode<()> for IssuerSignatureAlgorithm { impl Decode<'_, ()> for IssuerSignatureAlgorithm { fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { - match d.datatype()? { + match decode_datatype(d, "Issuer Signature Algorithm")? { // Check i16 for -256 and -256 minicbor::data::Type::U8 | minicbor::data::Type::I16 => { - let i = d.i16()?; + let i = decode_helper(d, "Issuer Signature Algorithm as OID int", ctx)?; let oid = get_oid_from_int(i).map_err(minicbor::decode::Error::message)?; Ok(Self::new(oid, None)) }, _ => { let algo_identifier = AlgorithmIdentifier::decode(d, ctx)?; Ok(IssuerSignatureAlgorithm::new( - algo_identifier.get_oid(), - algo_identifier.get_param().clone(), + algo_identifier.oid().clone(), + algo_identifier.param().clone(), )) }, } diff --git a/rust/c509-certificate/src/lib.rs b/rust/c509-certificate/src/lib.rs index 434631b2c82..b60c0cade77 100644 --- a/rust/c509-certificate/src/lib.rs +++ b/rust/c509-certificate/src/lib.rs @@ -2,7 +2,11 @@ //! //! This crate provides a functionality for generating C509 Certificate. //! -//! ## C509 certificate contains 2 parts +//! Please refer to the [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) for more information. +//! +//! ## C509 certificate structure +//! +//! A C509 certificate is a CBOR encoded X.509 certificate. It consists of two main parts: //! 1. `TBSCertificate` //! 2. `issuerSignatureValue` //! @@ -13,46 +17,49 @@ //! # TBS Certificate //! //! The To Be Sign Certificate contains the following fields: -//! * c509CertificateType: A certificate type, whether 0 a natively signed C509 -//! certificate following X.509 v3 or 1 a CBOR re-encoded X.509 v3 DER certificate. +//! * c509CertificateType: A certificate type, where 2 indicates a natively signed C509 +//! certificate following X.509 v3 or 3 indicates CBOR re-encoded X.509 v3 DER +//! certificate. //! * certificateSerialNumber: A unique serial number for the certificate. -//! * issuer: The entity that issued the certificate. +//! * subjectPublicKeyAlgorithm: Specifies the cryptographic algorithm used for the +//! `subjectPublicKey`. +//! * issuer: The entity that issued the certificate. In the case of a self-signed +//! certificate, the issuer is identical to the subject. //! * validityNotBefore: The duration for which the Certificate Authority (CA) //! guarantees it will retain information regarding the certificate's status on which //! the period begins. //! * validityNotAfter: The duration for which the Certificate Authority (CA) //! guarantees it will retain information regarding the certificate's status on which -//! the period ends. +//! the period ends. This can be set to no expiry date. //! * subject: The entity associated with the public key stored in the subject public //! key field. -//! * subjectPublicKeyAlgorithm: The algorithm that the public key is used. //! * subjectPublicKey: The public key of the subject. //! * extensions: A list of extensions defined for X.509 v3 certificate, providing //! additional attributes for users or public keys, and for managing relationships //! between Certificate Authorities (CAs). //! * issuerSignatureAlgorithm: The algorithm used to sign the certificate (must be the //! algorithm uses to create `IssuerSignatureValue`). -//! -//! Please refer to the [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) for more information. +// use anyhow::anyhow; use c509::C509; +use cert_tbs::TbsCert; use minicbor::{Decode, Encode}; use signing::{PrivateKey, PublicKey}; -use tbs_cert::TbsCert; pub mod algorithm_identifier; pub mod attributes; pub mod big_uint; pub mod c509; +pub mod cert_tbs; pub mod extensions; pub mod general_names; +mod helper; pub mod issuer_sig_algo; pub mod name; pub mod oid; pub mod signing; pub mod subject_pub_key_algo; mod tables; -pub mod tbs_cert; pub mod time; pub mod wasm_binding; @@ -67,7 +74,7 @@ pub mod wasm_binding; /// /// # Errors /// -/// Returns an error if tne data cannot be converted to CBOR bytes. +/// Returns an error if the generated data is invalid. pub fn generate(tbs_cert: &TbsCert, private_key: Option<&PrivateKey>) -> anyhow::Result> { // Encode the TbsCert @@ -104,8 +111,8 @@ pub fn verify(c509: &[u8], public_key: &PublicKey) -> anyhow::Result<()> { let c509 = C509::decode(&mut d, &mut ())?; let mut encoded_tbs = Vec::new(); let mut encoder = minicbor::Encoder::new(&mut encoded_tbs); - c509.get_tbs_cert().encode(&mut encoder, &mut ())?; - let issuer_sig = c509.get_issuer_signature_value().clone().ok_or(anyhow!( + c509.tbs_cert().encode(&mut encoder, &mut ())?; + let issuer_sig = c509.issuer_signature_value().clone().ok_or(anyhow!( "Signature verification failed, No issuer signature" ))?; public_key.verify(&encoded_tbs, &issuer_sig) @@ -115,14 +122,14 @@ pub fn verify(c509: &[u8], public_key: &PublicKey) -> anyhow::Result<()> { mod test { use std::str::FromStr; + use cert_tbs::test_tbs_cert::tbs_1; use signing::tests::private_key_str; - use tbs_cert::test_tbs_cert::tbs; use super::*; #[test] fn test_generate_and_verify_signed_c509_cert() { - let tbs_cert = tbs(); + let (tbs_cert, _) = tbs_1(); let private_key = FromStr::from_str(&private_key_str()).expect( "Cannot create diff --git a/rust/c509-certificate/src/name/mod.rs b/rust/c509-certificate/src/name/mod.rs index c4144390775..e385926f7d6 100644 --- a/rust/c509-certificate/src/name/mod.rs +++ b/rust/c509-certificate/src/name/mod.rs @@ -1,30 +1,29 @@ //! C509 type Name //! //! Currently only support natively signed c509 certificate, so all text strings -//! are UTF-8 encoded and all attributeType should be non-negative. +//! are UTF-8 encoded and all attributeType should be positive. //! //! ```cddl -//! Name = [ * RelativeDistinguishedName ] / text / bytes -//! RelativeDistinguishedName = Attribute / [ 2* Attribute ] +//! Name = [ * Attribute ] / text / bytes //! Attribute = ( attributeType: int, attributeValue: text ) // //! ( attributeType: ~oid, attributeValue: bytes ) // -//! ( attributeType: pen, attributeValue: bytes ) //! ``` //! //! For more information about Name, -//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) -// cspell: words rdns - -pub mod rdn; use asn1_rs::{oid, Oid}; use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; -use rdn::RelativeDistinguishedName; use regex::Regex; use serde::{Deserialize, Serialize}; -use crate::attributes::attribute::{Attribute, AttributeValue}; - +use crate::{ + attributes::attribute::{Attribute, AttributeValue}, + helper::{ + decode::{decode_array_len, decode_bytes, decode_datatype, decode_helper}, + encode::{encode_array_len, encode_bytes, encode_helper}, + }, +}; /// OID of `CommonName` attribute. const COMMON_NAME_OID: Oid<'static> = oid!(2.5.4 .3); /// EUI-64 prefix. @@ -51,7 +50,7 @@ impl Name { /// Get the value of the `Name`. #[must_use] - pub fn get_value(&self) -> &NameValue { + pub fn value(&self) -> &NameValue { &self.0 } } @@ -77,8 +76,8 @@ impl Decode<'_, ()> for Name { #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum NameValue { - /// A relative distinguished name. - RelativeDistinguishedName(RelativeDistinguishedName), + /// Attribute. + Attribute(Vec), /// A text. Text(String), /// bytes. @@ -90,34 +89,41 @@ impl Encode<()> for NameValue { &self, e: &mut Encoder, ctx: &mut (), ) -> Result<(), minicbor::encode::Error> { match self { - NameValue::RelativeDistinguishedName(rdn) => { - let attr = rdn.get_attributes(); - let attr_first = attr.first().ok_or(minicbor::encode::Error::message( - "Cannot get the first Attribute", - ))?; - // If Name contains a single Attribute of type CommonName - if attr.len() == 1 - && attr_first.get_registered_oid().get_c509_oid().get_oid() == COMMON_NAME_OID - { - // Get the value of the attribute - let cn_value = - attr_first - .get_value() - .first() - .ok_or(minicbor::encode::Error::message( - "Cannot get the first Attribute value", - ))?; - - encode_cn_value(e, cn_value)?; + NameValue::Attribute(attrs) => { + if let Some(attr_first) = attrs.first() { + // If `attrs` contains exactly one attribute of type CommonName + if attrs.len() == 1 + && attr_first.registered_oid().c509_oid().oid() == &COMMON_NAME_OID + { + // Get the value of the attribute + let cn_value = + attr_first + .value() + .first() + .ok_or(minicbor::encode::Error::message( + "Cannot get the first attribute value", + ))?; + + encode_cn_value(e, cn_value)?; + } else { + encode_array_len(e, "Attributes", attrs.len() as u64 * 2)?; + for attribute in attrs { + attribute.encode(e, ctx)?; + } + } } else { - rdn.encode(e, ctx)?; + // If is okay if the attributes is empty + encode_array_len(e, "Attributes", attrs.len() as u64 * 2)?; + for attribute in attrs { + attribute.encode(e, ctx)?; + } } }, NameValue::Text(text) => { - e.str(text)?; + encode_helper(e, "Name", ctx, text)?; }, NameValue::Bytes(bytes) => { - e.bytes(bytes)?; + encode_bytes(e, "Name", bytes)?; }, } Ok(()) @@ -126,15 +132,23 @@ impl Encode<()> for NameValue { impl Decode<'_, ()> for NameValue { fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { - match d.datatype()? { + match decode_datatype(d, "Name")? { minicbor::data::Type::Array => { - Ok(NameValue::RelativeDistinguishedName( - RelativeDistinguishedName::decode(d, ctx)?, - )) + let len = decode_array_len(d, "Attributes")?; + let mut attrs = Vec::new(); + + // The attribute type is included in an array, so divide by 2 + for _ in 0..len / 2 { + let attribute = Attribute::decode(d, ctx)?; + attrs.push(attribute); + } + Ok(NameValue::Attribute(attrs)) }, // If Name is a text string, the attribute is a CommonName - minicbor::data::Type::String => Ok(create_rdn_with_cn_attr(d.str()?.to_string())), - minicbor::data::Type::Bytes => decode_bytes(d), + minicbor::data::Type::String => { + Ok(create_attributes_with_cn(decode_helper(d, "Name", ctx)?)) + }, + minicbor::data::Type::Bytes => decode_bytes_helper(d), _ => { Err(minicbor::decode::Error::message( "Name must be an array, text or bytes", @@ -161,7 +175,11 @@ fn encode_cn_value( // string, prefixed with an initial byte set to '00' if hex_regex.is_match(s) && s.len() % 2 == 0 { let decoded_bytes = hex::decode(s).map_err(minicbor::encode::Error::message)?; - e.bytes(&[&[HEX_PREFIX], &decoded_bytes[..]].concat())?; + encode_bytes( + e, + "Common Name hex", + &[&[HEX_PREFIX], &decoded_bytes[..]].concat(), + )?; // An EUI-64 mapped from a 48-bit MAC address (i.e., of the form // "HH-HH-HH-FF-FE-HH-HH-HH) is encoded as a CBOR byte string prefixed with an @@ -180,7 +198,11 @@ fn encode_cn_value( .ok_or(minicbor::encode::Error::message( "Failed to get MAC EUI-64 bytes index 5 to 6", ))?; - e.bytes(&[&[EUI64_PREFIX], chunk2, chunk3].concat())?; + encode_bytes( + e, + "Common Name EUI-64 MAC", + &[&[EUI64_PREFIX], chunk2, chunk3].concat(), + )?; // an EUI-64 of the form "HH-HH-HH-HH-HH-HH-HH-HH" where 'H' // is one of the symbols '0'–'9' or 'A'–'F' it is encoded as a @@ -190,9 +212,13 @@ fn encode_cn_value( let clean_name = s.replace('-', ""); let decoded_bytes = hex::decode(clean_name).map_err(minicbor::encode::Error::message)?; - e.bytes(&[&[EUI64_PREFIX], &decoded_bytes[..]].concat())?; + encode_bytes( + e, + "Common Name EUI-64", + &[&[EUI64_PREFIX], &decoded_bytes[..]].concat(), + )?; } else { - e.str(s)?; + encode_helper(e, "Common Name", &mut (), s)?; } }, AttributeValue::Bytes(_) => { @@ -213,8 +239,8 @@ fn formatted_eui_bytes(data: &[u8]) -> String { } /// Decode bytes. -fn decode_bytes(d: &mut Decoder<'_>) -> Result { - let bytes = d.bytes()?; +fn decode_bytes_helper(d: &mut Decoder<'_>) -> Result { + let bytes = decode_bytes(d, "Name")?; let first_i = bytes.first().ok_or(minicbor::decode::Error::message( "Failed to get the first index of bytes", @@ -223,10 +249,10 @@ fn decode_bytes(d: &mut Decoder<'_>) -> Result decode_hex_cn_bytes(bytes), + HEX_PREFIX => decode_hex_cn_bytes(&bytes), // 0x01 for EUI - EUI64_PREFIX => decode_eui_cn_bytes(bytes), - _ => Ok(NameValue::Bytes(bytes.to_vec())), + EUI64_PREFIX => decode_eui_cn_bytes(&bytes), + _ => Ok(NameValue::Bytes(bytes)), } } @@ -235,7 +261,7 @@ fn decode_hex_cn_bytes(bytes: &[u8]) -> Result Result { let text = formatted_eui_bytes(bytes.get(1..).ok_or( minicbor::decode::Error::message("Failed to get EUI-64 bytes index"), )?); - Ok(create_rdn_with_cn_attr(text)) + Ok(create_attributes_with_cn(text)) }, _ => { Err(minicbor::decode::Error::message( @@ -270,47 +296,38 @@ fn decode_eui_cn_bytes(bytes: &[u8]) -> Result NameValue { +/// Create a attributes with attribute common name from string. +fn create_attributes_with_cn(text: String) -> NameValue { let mut attr = Attribute::new(COMMON_NAME_OID); attr.add_value(AttributeValue::Text(text)); - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr); - NameValue::RelativeDistinguishedName(rdn) + NameValue::Attribute(vec![attr]) } // ------------------Test---------------------- #[cfg(test)] -pub(crate) mod test_name { +mod test_name { + use std::vec; + use super::*; use crate::attributes::attribute::Attribute; - // Test data from https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/ + // Test data from https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/ // A.1.1. Example C509 Certificate Encoding - pub(crate) fn name_cn_text() -> (Name, String) { - let mut attr = Attribute::new(oid!(2.5.4 .3)); - attr.add_value(AttributeValue::Text("RFC test CA".to_string())); - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr); - - ( - Name::new(NameValue::RelativeDistinguishedName(rdn)), - // "RFC test CA" Text string: 6b5246432074657374204341 - "6b5246432074657374204341".to_string(), - ) - } - #[test] fn encode_decode_type_name_cn() { let mut buffer = Vec::new(); let mut encoder = Encoder::new(&mut buffer); - let name = name_cn_text().0; + let mut attr = Attribute::new(oid!(2.5.4 .3)); + attr.add_value(AttributeValue::Text("RFC test CA".to_string())); + + let name = Name::new(NameValue::Attribute(vec![attr])); name.encode(&mut encoder, &mut ()) .expect("Failed to encode Name"); - assert_eq!(hex::encode(buffer.clone()), name_cn_text().1); + // "RFC test CA" text(11): 0x6b5246432074657374204341 + assert_eq!(hex::encode(buffer.clone()), "6b5246432074657374204341"); let mut decoder = Decoder::new(&buffer); let name_decoded = Name::decode(&mut decoder, &mut ()).expect("Failed to decode Name"); @@ -324,10 +341,8 @@ pub(crate) mod test_name { let mut attr = Attribute::new(oid!(2.5.4 .3)); attr.add_value(AttributeValue::Text("000123abcd".to_string())); - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr); - let name = Name::new(NameValue::RelativeDistinguishedName(rdn)); + let name = Name::new(NameValue::Attribute(vec![attr])); name.encode(&mut encoder, &mut ()) .expect("Failed to encode Name"); @@ -348,10 +363,8 @@ pub(crate) mod test_name { let mut attr = Attribute::new(oid!(2.5.4 .3)); attr.add_value(AttributeValue::Text("000123ABCD".to_string())); - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr); - let name = Name::new(NameValue::RelativeDistinguishedName(rdn)); + let name = Name::new(NameValue::Attribute(vec![attr])); name.encode(&mut encoder, &mut ()) .expect("Failed to encode Name"); @@ -364,32 +377,21 @@ pub(crate) mod test_name { assert_eq!(name_decoded, name); } - // Test data from https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/ - // A.1. Example RFC 7925 profiled X.509 Certificate - pub(crate) fn name_cn_eui_mac() -> (Name, String) { - let mut attr = Attribute::new(oid!(2.5.4 .3)); - attr.add_value(AttributeValue::Text("01-23-45-FF-FE-67-89-AB".to_string())); - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr); - - ( - Name::new(NameValue::RelativeDistinguishedName(rdn)), - // Bytes of length 7: 0x47 - // "01-23-45-FF-FE-67-89-AB" special encode: 0x010123456789AB - "47010123456789ab".to_string(), - ) - } - #[test] fn encode_decode_type_name_cn_eui_mac() { let mut buffer = Vec::new(); let mut encoder = Encoder::new(&mut buffer); - let name = name_cn_eui_mac().0; + let mut attr = Attribute::new(oid!(2.5.4 .3)); + attr.add_value(AttributeValue::Text("01-23-45-FF-FE-67-89-AB".to_string())); + + let name = Name::new(NameValue::Attribute(vec![attr])); name.encode(&mut encoder, &mut ()) .expect("Failed to encode Name"); - assert_eq!(hex::encode(buffer.clone()), name_cn_eui_mac().1); + // Bytes of length 7: 0x47 + // "01-23-45-FF-FE-67-89-AB" special encode: 0x010123456789AB + assert_eq!(hex::encode(buffer.clone()), "47010123456789ab"); let mut decoder = Decoder::new(&buffer); let name_decoded = Name::decode(&mut decoder, &mut ()).expect("Failed to decode Name"); @@ -403,9 +405,7 @@ pub(crate) mod test_name { let mut attr = Attribute::new(oid!(2.5.4 .3)); attr.add_value(AttributeValue::Text("01-23-45-ff-fe-67-89-AB".to_string())); - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr); - let name = Name::new(NameValue::RelativeDistinguishedName(rdn)); + let name = Name::new(NameValue::Attribute(vec![attr])); name.encode(&mut encoder, &mut ()) .expect("Failed to encode Name"); @@ -429,14 +429,13 @@ pub(crate) mod test_name { let mut attr = Attribute::new(oid!(2.5.4 .3)); attr.add_value(AttributeValue::Text("01-23-45-67-89-AB-00-01".to_string())); - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr); - let name = Name::new(NameValue::RelativeDistinguishedName(rdn)); + let name = Name::new(NameValue::Attribute(vec![attr])); name.encode(&mut encoder, &mut ()) .expect("Failed to encode Name"); + // 01-23-45-67-89-AB-00-01 = h'010123456789AB0001': 0x49010123456789ab0001 assert_eq!(hex::encode(buffer.clone()), "49010123456789ab0001"); let mut decoder = Decoder::new(&buffer); @@ -451,10 +450,8 @@ pub(crate) mod test_name { let mut attr = Attribute::new(oid!(2.5.4 .3)); attr.add_value(AttributeValue::Text("01-23-45-67-89-ab-00-01".to_string())); - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr); - let name = Name::new(NameValue::RelativeDistinguishedName(rdn)); + let name = Name::new(NameValue::Attribute(vec![attr])); name.encode(&mut encoder, &mut ()) .expect("Failed to encode Name"); @@ -471,10 +468,14 @@ pub(crate) mod test_name { assert_eq!(name_decoded, name); } - // Test data from https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/ + // Test data from https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/ // A.2. Example IEEE 802.1AR profiled X.509 Certificate // Issuer: C=US, ST=CA, O=Example Inc, OU=certification, CN=802.1AR CA - pub(crate) fn names() -> (Name, String) { + #[test] + fn encode_decode_type_name_attrs() { + let mut buffer = Vec::new(); + let mut encoder = Encoder::new(&mut buffer); + let mut attr1 = Attribute::new(oid!(2.5.4 .6)); attr1.add_value(AttributeValue::Text("US".to_string())); let mut attr2 = Attribute::new(oid!(2.5.4 .8)); @@ -486,34 +487,30 @@ pub(crate) mod test_name { let mut attr5 = Attribute::new(oid!(2.5.4 .3)); attr5.add_value(AttributeValue::Text("802.1AR CA".to_string())); - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr1); - rdn.add_attr(attr2); - rdn.add_attr(attr3); - rdn.add_attr(attr4); - rdn.add_attr(attr5); - - ( - Name::new(NameValue::RelativeDistinguishedName(rdn)), - // Array of 10 items [4, "US", 6, "CA", 8, "Example Inc", 9, "certification", 1, "802.1AR CA"] : 0x8a - // attr1: 0x04625553 - // attr2: 0x06624341 - // attr3: 0x086b4578616d706c6520496e63 - // attr4: 0x096d63657274696669636174696f6e - // attr5: 0x016a3830322e314152204341 - "8a0462555306624341086b4578616d706c6520496e63096d63657274696669636174696f6e016a3830322e314152204341".to_string(), - ) + let name = Name::new(NameValue::Attribute(vec![ + attr1, attr2, attr3, attr4, attr5, + ])); + + name.encode(&mut encoder, &mut ()) + .expect("Failed to encode Name"); + assert_eq!(hex::encode(buffer.clone()), "8a0462555306624341086b4578616d706c6520496e63096d63657274696669636174696f6e016a3830322e314152204341"); + + let mut decoder = Decoder::new(&buffer); + let name_decoded = Name::decode(&mut decoder, &mut ()).expect("Failed to decode Name"); + assert_eq!(name_decoded, name); } + #[test] - fn encode_decode_type_name_rdns() { + fn encode_decode_empty_attribute() { let mut buffer = Vec::new(); let mut encoder = Encoder::new(&mut buffer); - let name = names().0; + let name = Name::new(NameValue::Attribute(vec![])); name.encode(&mut encoder, &mut ()) .expect("Failed to encode Name"); - assert_eq!(hex::encode(buffer.clone()), names().1); + + assert_eq!(hex::encode(buffer.clone()), "80"); let mut decoder = Decoder::new(&buffer); let name_decoded = Name::decode(&mut decoder, &mut ()).expect("Failed to decode Name"); diff --git a/rust/c509-certificate/src/name/rdn.rs b/rust/c509-certificate/src/name/rdn.rs deleted file mode 100644 index 13f5eb12f09..00000000000 --- a/rust/c509-certificate/src/name/rdn.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! C509 Relative Distinguished Name -//! -//! For more information about `RelativeDistinguishedName`, -//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) - -// cspell: words rdns - -use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; -use serde::{Deserialize, Serialize}; - -use crate::attributes::attribute::Attribute; - -/// A struct represents a Relative Distinguished Name containing vector of `Attribute`. -/// -/// ```cddl -/// RelativeDistinguishedName = Attribute / [ 2* Attribute ] -/// ``` -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct RelativeDistinguishedName(Vec); - -impl Default for RelativeDistinguishedName { - fn default() -> Self { - Self::new() - } -} - -impl RelativeDistinguishedName { - /// Create a new instance of `RelativeDistinguishedName` as empty vector. - #[must_use] - pub fn new() -> Self { - Self(Vec::new()) - } - - /// Add an `Attribute` to the `RelativeDistinguishedName`. - pub fn add_attr(&mut self, attribute: Attribute) { - // RelativeDistinguishedName support pen encoding - self.0.push(attribute.set_pen_supported()); - } - - /// Get the a vector of `Attribute`. - pub(crate) fn get_attributes(&self) -> &Vec { - &self.0 - } -} - -impl Encode<()> for RelativeDistinguishedName { - // ```cddl - // RelativeDistinguishedName = Attribute / [ 2* Attribute ] - // ``` - fn encode( - &self, e: &mut Encoder, ctx: &mut (), - ) -> Result<(), minicbor::encode::Error> { - // Should contain >= 1 attribute - if self.0.is_empty() { - return Err(minicbor::encode::Error::message( - "RelativeDistinguishedName should not be empty", - )); - } - - if self.0.len() == 1 { - self.0.first().encode(e, ctx)?; - } else { - // The attribute type should be included in array too - e.array(self.0.len() as u64 * 2)?; - for attr in &self.0 { - attr.encode(e, ctx)?; - } - } - Ok(()) - } -} - -impl Decode<'_, ()> for RelativeDistinguishedName { - fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { - let mut rdn = RelativeDistinguishedName::new(); - - match d.datatype()? { - minicbor::data::Type::Array => { - let len = d.array()?.ok_or(minicbor::decode::Error::message( - "Failed to get array length for relative distinguished name", - ))?; - // Should contain >= 1 attribute - if len == 0 { - return Err(minicbor::decode::Error::message( - "RelativeDistinguishedName should not be empty", - )); - } - // The attribute type is included in an array, so divide by 2 - for _ in 0..len / 2 { - rdn.add_attr(Attribute::decode(d, ctx)?); - } - }, - _ => rdn.add_attr(Attribute::decode(d, ctx)?), - } - Ok(rdn) - } -} - -// -------------------Test---------------------- - -#[cfg(test)] -mod test_relative_distinguished_name { - - use asn1_rs::oid; - - use super::*; - use crate::attributes::attribute::AttributeValue; - - #[test] - fn encode_decode_rdn() { - let mut buffer = Vec::new(); - let mut encoder = Encoder::new(&mut buffer); - - let mut attr = Attribute::new(oid!(1.2.840 .113549 .1 .9 .1)); - attr.add_value(AttributeValue::Text("example@example.com".to_string())); - - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr); - rdn.encode(&mut encoder, &mut ()) - .expect("Failed to encode RDN"); - // Email Address: 0x00 - // "example@example.como": 736578616d706c65406578616d706c652e636f6d - assert_eq!( - hex::encode(buffer.clone()), - "00736578616d706c65406578616d706c652e636f6d" - ); - - let mut decoder = Decoder::new(&buffer); - let rdn_decoded = RelativeDistinguishedName::decode(&mut decoder, &mut ()) - .expect("Failed to decode RelativeDistinguishedName"); - assert_eq!(rdn_decoded, rdn); - } - - #[test] - fn encode_decode_rdns() { - let mut buffer = Vec::new(); - let mut encoder = Encoder::new(&mut buffer); - - let mut attr1 = Attribute::new(oid!(1.2.840 .113549 .1 .9 .1)); - attr1.add_value(AttributeValue::Text("example@example.com".to_string())); - let mut attr2 = Attribute::new(oid!(2.5.4 .3)); - attr2.add_value(AttributeValue::Text("example".to_string())); - - let mut rdns = RelativeDistinguishedName::new(); - rdns.add_attr(attr1); - rdns.add_attr(attr2); - - rdns.encode(&mut encoder, &mut ()) - .expect("Failed to encode RDN"); - // Array of 2 attributes: 0x84 - // Email Address example@example.com: 0x00736578616d706c65406578616d706c652e636f6d - // Common Name example: 0x01676578616d706c65 - assert_eq!( - hex::encode(buffer.clone()), - "8400736578616d706c65406578616d706c652e636f6d01676578616d706c65" - ); - let mut decoder = Decoder::new(&buffer); - let rdn_decoded = RelativeDistinguishedName::decode(&mut decoder, &mut ()) - .expect("Failed to decode RelativeDistinguishedName"); - assert_eq!(rdn_decoded, rdns); - } - - #[test] - fn empty_rdn() { - let mut buffer = Vec::new(); - let mut encoder = Encoder::new(&mut buffer); - let rdn = RelativeDistinguishedName::new(); - rdn.encode(&mut encoder, &mut ()) - .expect_err("Failed to encode RDN"); - } -} diff --git a/rust/c509-certificate/src/oid.rs b/rust/c509-certificate/src/oid.rs index 2a8ed92fd9f..a3f45be75d7 100644 --- a/rust/c509-certificate/src/oid.rs +++ b/rust/c509-certificate/src/oid.rs @@ -7,25 +7,22 @@ use std::str::FromStr; use anyhow::Result; -use asn1_rs::oid; -use minicbor::{data::Tag, decode, encode::Write, Decode, Decoder, Encode, Encoder}; +use minicbor::{decode, encode::Write, Decode, Decoder, Encode, Encoder}; use oid_registry::Oid; use serde::{Deserialize, Deserializer, Serialize}; -use crate::tables::IntegerToOidTable; - -/// IANA Private Enterprise Number (PEN) OID prefix. -const PEN_PREFIX: Oid<'static> = oid!(1.3.6 .1 .4 .1); - -/// Tag number representing IANA Private Enterprise Number (PEN) OID. -const OID_PEN_TAG: u64 = 112; +use crate::{ + helper::{decode::decode_bytes, encode::encode_bytes}, + tables::IntegerToOidTable, +}; /// A strut of C509 OID with Registered Integer. #[derive(Debug, Clone, PartialEq)] pub struct C509oidRegistered { /// The `C509oid`. - oid: C509oid, - /// The registration table. + c509_oid: C509oid, + /// The registration lookup table for associated int to OID. + /// Each C509 field or type can have different registration table. registration_table: &'static IntegerToOidTable, } @@ -33,26 +30,19 @@ impl C509oidRegistered { /// Create a new instance of `C509oidRegistered`. pub(crate) fn new(oid: Oid<'static>, table: &'static IntegerToOidTable) -> Self { Self { - oid: C509oid::new(oid), + c509_oid: C509oid::new(oid), registration_table: table, } } - /// Is PEN Encoding supported for this OID. - /// Depends on each registration table. - pub(crate) fn pen_encoded(mut self) -> Self { - self.oid.pen_supported = true; - self - } - /// Get the `C509oid`. #[must_use] - pub fn get_c509_oid(&self) -> C509oid { - self.oid.clone() + pub fn c509_oid(&self) -> &C509oid { + &self.c509_oid } /// Get the registration table. - pub(crate) fn get_table(&self) -> &'static IntegerToOidTable { + pub(crate) fn table(&self) -> &'static IntegerToOidTable { self.registration_table } } @@ -61,12 +51,7 @@ impl C509oidRegistered { /// A struct represent an instance of `C509oid`. #[derive(Debug, PartialEq, Clone, Eq, Hash)] -pub struct C509oid { - /// The OID. - oid: Oid<'static>, - /// The flag to indicate whether PEN encoding is supported. - pen_supported: bool, -} +pub struct C509oid(Oid<'static>); /// A helper struct for deserialize and serialize `C509oid`. #[derive(Debug, Deserialize, Serialize)] @@ -75,6 +60,20 @@ struct Helper { oid: String, } +impl C509oid { + /// Create an new instance of `C509oid`. + #[must_use] + pub fn new(oid: Oid<'static>) -> Self { + Self(oid) + } + + /// Get the underlying OID of the `C509oid` + #[must_use] + pub fn oid(&self) -> &Oid<'static> { + &self.0 + } +} + impl<'de> Deserialize<'de> for C509oid { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { @@ -89,41 +88,15 @@ impl Serialize for C509oid { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { let helper = Helper { - oid: self.oid.to_string(), + oid: self.0.to_string(), }; helper.serialize(serializer) } } -impl C509oid { - /// Create an new instance of `C509oid`. - /// Default value of PEN flag is false - #[must_use] - pub fn new(oid: Oid<'static>) -> Self { - Self { - oid, - pen_supported: false, - } - } - - /// Is PEN Encoding supported for this OID - pub(crate) fn pen_encoded(mut self) -> Self { - self.pen_supported = true; - self - } - - /// Get the underlying OID of the `C509oid` - #[must_use] - pub fn get_oid(self) -> Oid<'static> { - self.oid.clone() - } -} - impl Encode<()> for C509oid { /// Encode an OID - /// If `pen_supported` flag is set, and OID start with a valid `PEN_PREFIX`, - /// it is encoded as PEN (Private Enterprise Number) - /// else encode as an unwrapped OID (~oid) - as bytes string without tag. + /// Encode as an unwrapped OID (~oid) - as bytes string without tag. /// /// # Returns /// @@ -132,68 +105,22 @@ impl Encode<()> for C509oid { fn encode( &self, e: &mut Encoder, _ctx: &mut (), ) -> Result<(), minicbor::encode::Error> { - // Check if PEN encoding is supported and the OID starts with the PEN prefix. - if self.pen_supported && self.oid.starts_with(&PEN_PREFIX) { - // Set the CBOR tag. - e.tag(Tag::new(OID_PEN_TAG))?; - // Convert OID originally store as [u8] to [u64] - // This process is necessary to get the correct OID - // For example given - 1.3.6 .1 .4 .1.4.999 - // This OID will be stored as [u8] - [43, 6, 1, 4, 1, 4, 135, 103] - // The first 2 integer has a special encoding formula where, - // values is computed using X * 40 + Y (See RFC9090 for more info) - // The number 999 exceed the 225 limit (max of u8), so it will be encoded as 2 bytes - let raw_oid: Vec = - self.oid - .iter() - .map(Iterator::collect) - .ok_or(minicbor::encode::Error::message( - "Failed to collect OID components from iterator", - ))?; - let raw_pen_prefix: Vec = PEN_PREFIX.iter().map(Iterator::collect).ok_or( - minicbor::encode::Error::message("Failed to collect OID components from iterator"), - )?; - // relative_oid is OID that follows PEN_PREFIX (relative to PEN_PREFIX) - // Use the [u64] PEN prefix length to extract the relative OID - let oid_slice = - raw_oid - .get(raw_pen_prefix.len()..) - .ok_or(minicbor::encode::Error::message( - "Failed to get a OID slice", - ))?; - let relative_oid = Oid::from_relative(oid_slice) - .map_err(|_| minicbor::encode::Error::message("Failed to get a relative OID"))?; - return e.bytes(relative_oid.as_bytes())?.ok(); - } - let oid_bytes = self.oid.as_bytes(); - e.bytes(oid_bytes)?.ok() + let oid_bytes = self.0.as_bytes(); + encode_bytes(e, "C509 OID", oid_bytes) } } impl Decode<'_, ()> for C509oid { /// Decode an OID - /// If the data to be decoded is a `Tag`, and the tag is an `OID_PEN_TAG`, - /// then decode the OID as Private Enterprise Number (PEN) OID. - /// else decode the OID as unwrapped OID (~oid) - as bytes string without tag. + /// Decode the OID as unwrapped OID (~oid) - as bytes string without tag. /// # Returns /// /// A C509oid instance. /// If the decoding fails, it will return an error. fn decode(d: &mut Decoder, _ctx: &mut ()) -> Result { - if (minicbor::data::Type::Tag == d.datatype()?) && (Tag::new(OID_PEN_TAG) == d.tag()?) { - let oid_bytes = d.bytes()?; - // raw_oid contains the whole OID which stored in bytes - let mut raw_oid = Vec::new(); - raw_oid.extend_from_slice(PEN_PREFIX.as_bytes()); - raw_oid.extend_from_slice(oid_bytes); - // Convert the raw_oid to Oid - let oid = Oid::new(raw_oid.into()); - return Ok(C509oid::new(oid).pen_encoded()); - } - // Not a PEN Relative OID, so treat as a normal OID - let oid_bytes = d.bytes()?; - let oid = Oid::new(oid_bytes.to_owned().into()); + let oid_bytes = decode_bytes(d, "C509 OID")?; + let oid = Oid::new(oid_bytes.into()); Ok(C509oid::new(oid)) } } @@ -203,6 +130,8 @@ impl Decode<'_, ()> for C509oid { #[cfg(test)] mod test_c509_oid { + use asn1_rs::oid; + use super::*; // Test reference 3.1. Encoding of the SHA-256 OID @@ -214,6 +143,9 @@ mod test_c509_oid { let oid = C509oid::new(oid!(2.16.840 .1 .101 .3 .4 .2 .1)); oid.encode(&mut encoder, &mut ()) .expect("Failed to encode OID"); + // bytes(9) 0x49 + // 0x60 (for 2.16) + // 0x18, 0x03, 0x60, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 assert_eq!(hex::encode(buffer.clone()), "49608648016503040201"); let mut decoder = Decoder::new(&buffer); @@ -221,20 +153,6 @@ mod test_c509_oid { assert_eq!(decoded_oid, oid); } - #[test] - fn encode_decode_pen() { - let mut buffer = Vec::new(); - let mut encoder = Encoder::new(&mut buffer); - let oid = C509oid::new(oid!(1.3.6 .1 .4 .1 .1 .1 .29)).pen_encoded(); - oid.encode(&mut encoder, &mut ()) - .expect("Failed to encode OID"); - assert_eq!(hex::encode(buffer.clone()), "d8704301011d"); - - let mut decoder = Decoder::new(&buffer); - let decoded_oid = C509oid::decode(&mut decoder, &mut ()).expect("Failed to decode OID"); - assert_eq!(decoded_oid, oid); - } - #[test] fn partial_equal() { let oid1 = C509oid::new(oid_registry::OID_HASH_SHA1); diff --git a/rust/c509-certificate/src/subject_pub_key_algo/data.rs b/rust/c509-certificate/src/subject_pub_key_algo/data.rs index bb37a2d4bf5..0a29de39f48 100644 --- a/rust/c509-certificate/src/subject_pub_key_algo/data.rs +++ b/rust/c509-certificate/src/subject_pub_key_algo/data.rs @@ -1,5 +1,5 @@ //! Public key algorithm data provides a necessary information for encoding and decoding -//! of C509 `subjectPublicKeyAlgorithm`. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/) +//! of C509 `subjectPublicKeyAlgorithm`. See [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) //! Section 9.11 C509 Public Key Algorithms Registry for more information. // cspell: words Weierstraß secp XMSS brainpool diff --git a/rust/c509-certificate/src/subject_pub_key_algo/mod.rs b/rust/c509-certificate/src/subject_pub_key_algo/mod.rs index 014ad6e6cc7..b3d923bd1c7 100644 --- a/rust/c509-certificate/src/subject_pub_key_algo/mod.rs +++ b/rust/c509-certificate/src/subject_pub_key_algo/mod.rs @@ -1,10 +1,12 @@ -//! C509 Issuer Signature Algorithm as a part of `TBSCertificate` used in C509 +//! C509 Issuer Signature Algorithm //! Certificate. //! //! ```cddl //! subjectPublicKeyAlgorithm: AlgorithmIdentifier //! ``` - +//! +//! For more information about `subjectPublicKeyAlgorithm`, +//! visit [C509 Certificate](https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/) // cspell: words spka mod data; @@ -16,7 +18,14 @@ use data::{get_oid_from_int, SUBJECT_PUB_KEY_ALGO_LOOKUP}; use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Deserializer, Serialize}; -use crate::{algorithm_identifier::AlgorithmIdentifier, oid::C509oidRegistered}; +use crate::{ + algorithm_identifier::AlgorithmIdentifier, + helper::{ + decode::{decode_datatype, decode_helper}, + encode::encode_helper, + }, + oid::C509oidRegistered, +}; /// A struct represents the `SubjectPubKeyAlgorithm` #[derive(Debug, Clone, PartialEq)] @@ -39,6 +48,18 @@ impl SubjectPubKeyAlgorithm { algo_identifier: AlgorithmIdentifier::new(oid, param), } } + + /// Get the algorithm identifier. + #[must_use] + pub fn algo_identifier(&self) -> &AlgorithmIdentifier { + &self.algo_identifier + } + + /// Get the registered OID. + #[allow(dead_code)] + pub(crate) fn registered_oid(&self) -> &C509oidRegistered { + &self.registered_oid + } } /// Helper struct for deserialize and serialize `SubjectPubKeyAlgorithm`. @@ -65,8 +86,8 @@ impl Serialize for SubjectPubKeyAlgorithm { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { let helper = Helper { - oid: self.registered_oid.get_c509_oid().get_oid().to_string(), - param: self.algo_identifier.get_param().clone(), + oid: self.registered_oid.c509_oid().oid().to_string(), + param: self.algo_identifier.param().clone(), }; helper.serialize(serializer) } @@ -78,11 +99,11 @@ impl Encode<()> for SubjectPubKeyAlgorithm { ) -> Result<(), minicbor::encode::Error> { if let Some(&i) = self .registered_oid - .get_table() + .table() .get_map() - .get_by_right(&self.registered_oid.get_c509_oid().get_oid()) + .get_by_right(self.registered_oid.c509_oid().oid()) { - e.i16(i)?; + encode_helper(e, "Subject public key algorithm", ctx, &i)?; } else { AlgorithmIdentifier::encode(&self.algo_identifier, e, ctx)?; } @@ -93,15 +114,15 @@ impl Encode<()> for SubjectPubKeyAlgorithm { impl Decode<'_, ()> for SubjectPubKeyAlgorithm { fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { // Check u8 for 0 - 28 - if d.datatype()? == minicbor::data::Type::U8 { - let i = d.i16()?; + if decode_datatype(d, "Subject public key algorithm")? == minicbor::data::Type::U8 { + let i = decode_helper(d, "Subject public key algorithm", ctx)?; let oid = get_oid_from_int(i).map_err(minicbor::decode::Error::message)?; Ok(Self::new(oid, None)) } else { let algo_identifier = AlgorithmIdentifier::decode(d, ctx)?; Ok(SubjectPubKeyAlgorithm::new( - algo_identifier.get_oid(), - algo_identifier.get_param().clone(), + algo_identifier.oid().clone(), + algo_identifier.param().clone(), )) } } diff --git a/rust/c509-certificate/src/tbs_cert.rs b/rust/c509-certificate/src/tbs_cert.rs deleted file mode 100644 index d8bc83ffc78..00000000000 --- a/rust/c509-certificate/src/tbs_cert.rs +++ /dev/null @@ -1,478 +0,0 @@ -//! To Be Sign Certificate (TBS Certificate) use to construct a C509 certificate. - -use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; -use serde::{Deserialize, Serialize}; - -use crate::{ - big_uint::UnwrappedBigUint, extensions::Extensions, issuer_sig_algo::IssuerSignatureAlgorithm, - name::Name, subject_pub_key_algo::SubjectPubKeyAlgorithm, time::Time, -}; - -/// A struct represents a To Be Signed Certificate (TBS Certificate). -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub struct TbsCert { - /// Certificate type. - c509_certificate_type: u8, - /// Serial number of the certificate. - certificate_serial_number: UnwrappedBigUint, - /// Issuer - issuer: Name, - /// Validity not before. - validity_not_before: Time, - /// Validity not after. - validity_not_after: Time, - /// Subject - subject: Name, - /// Subject Public Key Algorithm - subject_public_key_algorithm: SubjectPubKeyAlgorithm, - /// Subject Public Key value - subject_public_key: Vec, - /// Extensions - extensions: Extensions, - /// Issuer Signature Algorithm - issuer_signature_algorithm: IssuerSignatureAlgorithm, -} - -impl TbsCert { - /// Create a new instance of TBS Certificate. - #[must_use] - #[allow(clippy::too_many_arguments)] - pub fn new( - c509_certificate_type: u8, certificate_serial_number: UnwrappedBigUint, issuer: Name, - validity_not_before: Time, validity_not_after: Time, subject: Name, - subject_public_key_algorithm: SubjectPubKeyAlgorithm, subject_public_key: Vec, - extensions: Extensions, issuer_signature_algorithm: IssuerSignatureAlgorithm, - ) -> Self { - Self { - c509_certificate_type, - certificate_serial_number, - issuer, - validity_not_before, - validity_not_after, - subject, - subject_public_key_algorithm, - subject_public_key, - extensions, - issuer_signature_algorithm, - } - } - - /// Get the certificate type. - #[must_use] - pub fn get_c509_certificate_type(&self) -> u8 { - self.c509_certificate_type - } - - /// Get the certificate serial number. - #[must_use] - pub fn get_certificate_serial_number(&self) -> &UnwrappedBigUint { - &self.certificate_serial_number - } - - /// Get the issuer. - #[must_use] - pub fn get_issuer(&self) -> &Name { - &self.issuer - } - - /// Get the validity not before. - #[must_use] - pub fn get_validity_not_before(&self) -> &Time { - &self.validity_not_before - } - - /// Get the validity not after. - #[must_use] - pub fn get_validity_not_after(&self) -> &Time { - &self.validity_not_after - } - - /// Get the subject. - #[must_use] - pub fn get_subject(&self) -> &Name { - &self.subject - } - - /// Get the subject public key algorithm. - #[must_use] - pub fn get_subject_public_key_algorithm(&self) -> &SubjectPubKeyAlgorithm { - &self.subject_public_key_algorithm - } - - /// Get the subject public key. - #[must_use] - pub fn get_subject_public_key(&self) -> &[u8] { - &self.subject_public_key - } - - /// Get the extensions. - #[must_use] - pub fn get_extensions(&self) -> &Extensions { - &self.extensions - } - - /// Get the issuer signature algorithm. - #[must_use] - pub fn get_issuer_signature_algorithm(&self) -> &IssuerSignatureAlgorithm { - &self.issuer_signature_algorithm - } -} - -impl Encode<()> for TbsCert { - fn encode( - &self, e: &mut Encoder, ctx: &mut (), - ) -> Result<(), minicbor::encode::Error> { - e.u8(self.c509_certificate_type)?; - self.certificate_serial_number.encode(e, ctx)?; - self.issuer.encode(e, ctx)?; - self.validity_not_before.encode(e, ctx)?; - self.validity_not_after.encode(e, ctx)?; - self.subject.encode(e, ctx)?; - self.subject_public_key_algorithm.encode(e, ctx)?; - e.bytes(&self.subject_public_key)?; - self.extensions.encode(e, ctx)?; - self.issuer_signature_algorithm.encode(e, ctx)?; - Ok(()) - } -} - -impl Decode<'_, ()> for TbsCert { - fn decode(d: &mut Decoder<'_>, ctx: &mut ()) -> Result { - let cert_type = d.u8()?; - let serial_number = UnwrappedBigUint::decode(d, ctx)?; - let issuer = Name::decode(d, ctx)?; - let not_before = Time::decode(d, ctx)?; - let not_after = Time::decode(d, ctx)?; - let subject = Name::decode(d, ctx)?; - let subject_public_key_algorithm = SubjectPubKeyAlgorithm::decode(d, ctx)?; - let subject_public_key = d.bytes()?; - let extensions = Extensions::decode(d, ctx)?; - let issuer_signature_algorithm = IssuerSignatureAlgorithm::decode(d, ctx)?; - - Ok(TbsCert::new( - cert_type, - serial_number, - issuer, - not_before, - not_after, - subject, - subject_public_key_algorithm, - subject_public_key.to_vec(), - extensions, - issuer_signature_algorithm, - )) - } -} - -// ------------------Test---------------------- - -// Notes -// - Test is modified to match the current encode and decode where `subject_public_key` -// doesn't support -// special case for rsaEncryption and id-ecPublicKey. -// - Currently support natively signed c509 certificate, so all text strings -// are UTF-8 encoded and all attributeType SHALL be non-negative -// - Some Extension values are not supported yet. - -#[cfg(test)] -pub(crate) mod test_tbs_cert { - use asn1_rs::oid; - - use super::*; - use crate::{ - attributes::attribute::{Attribute, AttributeValue}, - extensions::{ - alt_name::{AlternativeName, GeneralNamesOrText}, - extension::{Extension, ExtensionValue}, - }, - general_names::{ - general_name::{GeneralName, GeneralNameTypeRegistry, GeneralNameValue}, - other_name_hw_module::OtherNameHardwareModuleName, - GeneralNames, - }, - name::{ - rdn::RelativeDistinguishedName, - test_name::{name_cn_eui_mac, name_cn_text, names}, - NameValue, - }, - }; - - // Mnemonic: match mad promote group rival case - const PUBKEY: [u8; 8] = [0x88, 0xD0, 0xB6, 0xB0, 0xB3, 0x7B, 0xAA, 0x46]; - - // Test reference https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/ - // A.1. Example RFC 7925 profiled X.509 Certificate - // - // - // Certificate: - // Data: - // Version: 3 (0x2) - // Serial Number: 128269 (0x1f50d) - // Signature Algorithm: ecdsa-with-SHA256 - // Issuer: CN=RFC test CA - // Validity - // Not Before: Jan 1 00:00:00 2023 GMT - // Not After : Jan 1 00:00:00 2026 GMT - // Subject: CN=01-23-45-FF-FE-67-89-AB - // Subject Public Key Info: - // Public Key Algorithm: id-ecPublicKey - // Public-Key: (256 bit) - // pub: - // 04:b1:21:6a:b9:6e:5b:3b:33:40:f5:bd:f0:2e:69: - // 3f:16:21:3a:04:52:5e:d4:44:50:b1:01:9c:2d:fd: - // 38:38:ab:ac:4e:14:d8:6c:09:83:ed:5e:9e:ef:24: - // 48:c6:86:1c:c4:06:54:71:77:e6:02:60:30:d0:51: - // f7:79:2a:c2:06 - // ASN1 OID: prime256v1 - // NIST CURVE: P-256 - // X509v3 extensions: - // X509v3 Key Usage: - // Digital Signature - // Signature Algorithm: ecdsa-with-SHA256 - // 30:46:02:21:00:d4:32:0b:1d:68:49:e3:09:21:9d:30:03:7e: - // 13:81:66:f2:50:82:47:dd:da:e7:6c:ce:ea:55:05:3c:10:8e: - // 90:02:21:00:d5:51:f6:d6:01:06:f1:ab:b4:84:cf:be:62:56: - // c1:78:e4:ac:33:14:ea:19:19:1e:8b:60:7d:a5:ae:3b:da:16 - // - // 01 - // 43 01 F5 0D - // 6B 52 46 43 20 74 65 73 74 20 43 41 - // 1A 63 B0 CD 00 - // 1A 69 55 B9 00 - // 47 01 01 23 45 67 89 AB - // 01 - // 58 21 02 B1 21 6A B9 6E 5B 3B 33 40 F5 BD F0 2E 69 3F 16 21 3A 04 52 - // 5E D4 44 50 B1 01 9C 2D FD 38 38 AB - // 01 - // 00 - // 58 40 D4 32 0B 1D 68 49 E3 09 21 9D 30 03 7E 13 81 66 F2 50 82 47 DD - // DA E7 6C CE EA 55 05 3C 10 8E 90 D5 51 F6 D6 01 06 F1 AB B4 84 CF BE - // 62 56 C1 78 E4 AC 33 14 EA 19 19 1E 8B 60 7D A5 AE 3B DA 16 - - pub(crate) fn tbs() -> TbsCert { - fn extensions() -> Extensions { - let mut exts = Extensions::new(); - exts.add_ext(Extension::new( - oid!(2.5.29 .15), - ExtensionValue::Int(1), - false, - )); - exts - } - - TbsCert::new( - 1, - UnwrappedBigUint::new(128_269), - name_cn_text().0, - Time::new(1_672_531_200), - Time::new(1_767_225_600), - name_cn_eui_mac().0, - SubjectPubKeyAlgorithm::new(oid!(1.2.840 .10045 .2 .1), None), - PUBKEY.to_vec(), - extensions(), - IssuerSignatureAlgorithm::new(oid!(1.2.840 .10045 .4 .3 .2), None), - ) - } - - #[test] - fn encode_decode_tbs_cert() { - let tbs_cert = tbs(); - - let mut buffer = Vec::new(); - let mut encoder = Encoder::new(&mut buffer); - tbs_cert - .encode(&mut encoder, &mut ()) - .expect("Failed to encode TBS Certificate"); - - // c509_certificate_type: 0x01 - // certificate_serial_number: 0x4301f50d - // issuer: 0x6b5246432074657374204341 - // validity_not_before: 0x1a63b0cd00 - // validity_not_after: 0x1a6955b900 - // subject: 0x47010123456789ab - // subject_public_key_algorithm: 0x01 - // subject_public_key: 0x4888d0b6b0b37baa46 - // extensions: 0x01 - // issuer_signature_algorithm: 0x00 - - assert_eq!( - hex::encode(buffer.clone()), - "014301f50d6b52464320746573742043411a63b0cd001a6955b90047010123456789ab014888d0b6b0b37baa460100" - ); - - let mut decoder = Decoder::new(&buffer); - let decoded_tbs = - TbsCert::decode(&mut decoder, &mut ()).expect("Failed to decode TBS Certificate"); - assert_eq!(decoded_tbs, tbs_cert); - } - - // Test reference https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/ - // A.2. Example IEEE 802.1AR profiled X.509 Certificate - // - // Certificate: - // Data: - // Version: 3 (0x2) - // Serial Number: 9112578475118446130 (0x7e7661d7b54e4632) - // Signature Algorithm: ecdsa-with-SHA256 - // Issuer: C=US, ST=CA, O=Example Inc, OU=certification, CN=802.1AR CA - // Validity - // Not Before: Jan 31 11:29:16 2019 GMT - // Not After : Dec 31 23:59:59 9999 GMT - // Subject: C=US, ST=CA, L=LA, O=example Inc, OU=IoT/serialNumber=Wt1234 - // Subject Public Key Info: - // Public Key Algorithm: id-ecPublicKey - // Public-Key: (256 bit) - // pub: - // 04:c8:b4:21:f1:1c:25:e4:7e:3a:c5:71:23:bf:2d: - // 9f:dc:49:4f:02:8b:c3:51:cc:80:c0:3f:15:0b:f5: - // 0c:ff:95:8d:75:41:9d:81:a6:a2:45:df:fa:e7:90: - // be:95:cf:75:f6:02:f9:15:26:18:f8:16:a2:b2:3b: - // 56:38:e5:9f:d9 - // ASN1 OID: prime256v1 - // NIST CURVE: P-256 - // X509v3 extensions: - // X509v3 Basic Constraints: - // CA:FALSE - // X509v3 Subject Key Identifier: - // 96:60:0D:87:16:BF:7F:D0:E7:52:D0:AC:76:07:77:AD:66:5D:02:A0 - // X509v3 Authority Key Identifier: - // 68:D1:65:51:F9:51:BF:C8:2A:43:1D:0D:9F:08:BC:2D:20:5B:11:60 - // X509v3 Key Usage: critical - // Digital Signature, Key Encipherment - // X509v3 Subject Alternative Name: - // otherName: - // type-id: 1.3.6.1.5.5.7.8.4 (id-on-hardwareModuleName) - // value: - // hwType: 1.3.6.1.4.1.6175.10.1 - // hwSerialNum: 01:02:03:04 - // Signature Algorithm: ecdsa-with-SHA256 - // Signature Value: - // 30:46:02:21:00:c0:d8:19:96:d2:50:7d:69:3f:3c:48:ea:a5: - // ee:94:91:bd:a6:db:21:40:99:d9:81:17:c6:3b:36:13:74:cd: - // 86:02:21:00:a7:74:98:9f:4c:32:1a:5c:f2:5d:83:2a:4d:33: - // 6a:08:ad:67:df:20:f1:50:64:21:18:8a:0a:de:6d:34:92:36 - // - // 01 48 7E 76 61 D7 B5 4E 46 32 8A 23 62 55 53 06 62 43 41 08 6B 45 78 - // 61 6D 70 6C 65 20 49 6E 63 09 6D 63 65 72 74 69 66 69 63 61 74 69 6F - // 6E 01 6A 38 30 32 2E 31 41 52 20 43 41 1A 5C 52 DC 0C F6 8C 23 62 55 - // 53 06 62 43 41 05 62 4C 41 08 6B 65 78 61 6D 70 6C 65 20 49 6E 63 09 - // 63 49 6F 54 22 66 57 74 31 32 33 34 01 58 21 03 C8 B4 21 F1 1C 25 E4 - // 7E 3A C5 71 23 BF 2D 9F DC 49 4F 02 8B C3 51 CC 80 C0 3F 15 0B F5 0C - // FF 95 8A 04 21 01 54 96 60 0D 87 16 BF 7F D0 E7 52 D0 AC 76 07 77 AD - // 66 5D 02 A0 07 54 68 D1 65 51 F9 51 BF C8 2A 43 1D 0D 9F 08 BC 2D 20 - // 5B 11 60 21 05 03 82 20 82 49 2B 06 01 04 01 B0 1F 0A 01 44 01 02 03 - // 04 00 58 40 C0 D8 19 96 D2 50 7D 69 3F 3C 48 EA A5 EE 94 91 BD A6 DB - // 21 40 99 D9 81 17 C6 3B 36 13 74 CD 86 A7 74 98 9F 4C 32 1A 5C F2 5D - // 83 2A 4D 33 6A 08 AD 67 DF 20 F1 50 64 21 18 8A 0A DE 6D 34 92 36 - - #[test] - fn tbs_cert2() { - // ---------helper---------- - // C=US, ST=CA, L=LA, O=example Inc, OU=IoT/serialNumber=Wt1234 - fn subject() -> Name { - let mut attr1 = Attribute::new(oid!(2.5.4 .6)); - attr1.add_value(AttributeValue::Text("US".to_string())); - let mut attr2 = Attribute::new(oid!(2.5.4 .8)); - attr2.add_value(AttributeValue::Text("CA".to_string())); - let mut attr3 = Attribute::new(oid!(2.5.4 .7)); - attr3.add_value(AttributeValue::Text("LA".to_string())); - let mut attr4 = Attribute::new(oid!(2.5.4 .10)); - attr4.add_value(AttributeValue::Text("example Inc".to_string())); - let mut attr5 = Attribute::new(oid!(2.5.4 .11)); - attr5.add_value(AttributeValue::Text("IoT".to_string())); - let mut attr6 = Attribute::new(oid!(2.5.4 .5)); - attr6.add_value(AttributeValue::Text("Wt1234".to_string())); - - let mut rdn = RelativeDistinguishedName::new(); - rdn.add_attr(attr1); - rdn.add_attr(attr2); - rdn.add_attr(attr3); - rdn.add_attr(attr4); - rdn.add_attr(attr5); - rdn.add_attr(attr6); - - Name::new(NameValue::RelativeDistinguishedName(rdn)) - } - - fn extensions() -> Extensions { - let mut exts = Extensions::new(); - exts.add_ext(Extension::new( - oid!(2.5.29 .19), - ExtensionValue::Int(-2), - false, - )); - exts.add_ext(Extension::new( - oid!(2.5.29 .14), - ExtensionValue::Bytes( - [ - 0x96, 0x60, 0x0D, 0x87, 0x16, 0xBF, 0x7F, 0xD0, 0xE7, 0x52, 0xD0, 0xAC, - 0x76, 0x07, 0x77, 0xAD, 0x66, 0x5D, 0x02, 0xA0, - ] - .to_vec(), - ), - false, - )); - exts.add_ext(Extension::new( - oid!(2.5.29 .15), - ExtensionValue::Int(5), - true, - )); - let mut gns = GeneralNames::new(); - let hw = OtherNameHardwareModuleName::new(oid!(1.3.6 .1 .4 .1 .6175 .10 .1), vec![ - 0x01, 0x02, 0x03, 0x04, - ]); - gns.add_gn(GeneralName::new( - GeneralNameTypeRegistry::OtherNameHardwareModuleName, - GeneralNameValue::OtherNameHWModuleName(hw), - )); - - exts.add_ext(Extension::new( - oid!(2.5.29 .17), - ExtensionValue::AlternativeName(AlternativeName::new( - GeneralNamesOrText::GeneralNames(gns), - )), - false, - )); - - exts - } - - let tbs_cert = TbsCert::new( - 1, - UnwrappedBigUint::new(9_112_578_475_118_446_130), - names().0, - Time::new(1_548_934_156), - Time::new(253_402_300_799), - subject(), - SubjectPubKeyAlgorithm::new(oid!(1.2.840 .10045 .2 .1), None), - PUBKEY.to_vec(), - extensions(), - IssuerSignatureAlgorithm::new(oid!(1.2.840 .10045 .4 .3 .2), None), - ); - - let mut buffer = Vec::new(); - let mut encoder = Encoder::new(&mut buffer); - tbs_cert - .encode(&mut encoder, &mut ()) - .expect("Failed to encode TBS Certificate"); - // c509_certificate_type: 0x01 - // certificate_serial_number: 0x487e7661d7b54e4632 - // issuer: 0x8a0462555306624341086b4578616d706c6520496e63096d63657274696669636174696f6e016a3830322e314152204341 - // validity_not_before: 0x1a5c52dc0c - // validity_not_after: 0xf6 - // subject: 0x8c046255530662434105624c41086b6578616d706c6520496e630963496f540366577431323334 - // subject_public_key_algorithm: 0x01 - // subject_public_key: 0x4888d0b6b0b37baa46 - // extensions: - // 0x840421015496600d8716bf7fd0e752d0ac760777ad665d02a0210503822082492b06010401b01f0a014401020304 - // issuer_signature_algorithm: 0x00 - assert_eq!(hex::encode(buffer.clone()), - "01487e7661d7b54e46328a0462555306624341086b4578616d706c6520496e63096d63657274696669636174696f6e016a3830322e3141522043411a5c52dc0cf68c046255530662434105624c41086b6578616d706c6520496e630963496f540366577431323334014888d0b6b0b37baa46840421015496600d8716bf7fd0e752d0ac760777ad665d02a0210503822082492b06010401b01f0a01440102030400" - ); - let mut decoder = Decoder::new(&buffer); - let decoded_tbs = - TbsCert::decode(&mut decoder, &mut ()).expect("Failed to decode TBS Certificate"); - assert_eq!(decoded_tbs, tbs_cert); - } -} diff --git a/rust/c509-certificate/src/time.rs b/rust/c509-certificate/src/time.rs index 48c370f22e6..b3e74a178f9 100644 --- a/rust/c509-certificate/src/time.rs +++ b/rust/c509-certificate/src/time.rs @@ -3,35 +3,54 @@ use minicbor::{encode::Write, Decode, Decoder, Encode, Encoder}; use serde::{Deserialize, Serialize}; +use crate::helper::{ + decode::{decode_datatype, decode_helper, decode_null}, + encode::{encode_helper, encode_null}, +}; + /// A struct representing a time where it accept seconds since the Unix epoch. +/// Doesn't support dates before the Unix epoch (January 1, 1970, 00:00:00 UTC) +/// so unsigned integer is used. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct Time(i64); +pub struct Time(u64); /// No expiration date in seconds since the Unix epoch. -const NO_EXP_DATE: i64 = 253_402_300_799; +const NO_EXP_DATE: u64 = 253_402_300_799; impl Time { /// Create a new instance of `Time`. #[must_use] - pub fn new(time: i64) -> Self { + pub fn new(time: u64) -> Self { Self(time) } - /// Get the time in i64. + /// Get the u64 of `Time`. #[must_use] - pub fn to_i64(&self) -> i64 { + pub fn time(&self) -> u64 { self.0 } } +impl From for Time { + fn from(value: u64) -> Self { + Time::new(value) + } +} + +impl From