diff --git a/src/generated.rs b/src/generated.rs index 7e70e6a..e56b305 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -5,16 +5,16 @@ //! //! This file is automatically generated by build.rs - do not edit manually. -#[path = "generated/k_y_c_attributes.rs"] -mod k_y_c_attributes; #[path = "generated/sensitive_attributes.rs"] mod sensitive_attributes; +#[path = "generated/k_y_c_attributes.rs"] +mod k_y_c_attributes; -#[path = "generated/builder_ext.rs"] -pub mod builder_ext; #[path = "generated/from_impls.rs"] mod from_impls; +#[path = "generated/builder_ext.rs"] +pub mod builder_ext; // Re-export all types from the generated modules -pub use k_y_c_attributes::{Attribute, AttributeValue, KYCAttributes}; -pub use sensitive_attributes::{SensitiveAttribute, SensitiveAttributeCipher, SensitiveAttributeHashedValue}; +pub use sensitive_attributes::{SensitiveAttributeCipher, SensitiveAttributeHashedValue, SensitiveAttribute}; +pub use k_y_c_attributes::{AttributeValue, Attribute, KYCAttributes}; diff --git a/src/generated/builder_ext.rs b/src/generated/builder_ext.rs index b44c409..ee9dc88 100644 --- a/src/generated/builder_ext.rs +++ b/src/generated/builder_ext.rs @@ -3,9 +3,9 @@ //! This module provides a trait extension that adds `for_*` methods for all //! available KYC attributes, making the builder API more ergonomic and type-safe. -use crate::asn1::oids; -use crate::generated::Attribute; use crate::kyc_schema::builder::AttributeBuilderLike; +use crate::generated::Attribute; +use crate::asn1::oids; /// Extension trait for [`AttributeBuilderLike`] providing typed methods for KYC attributes. /// @@ -29,6 +29,118 @@ pub trait AttributeBuilderExtensions: AttributeBuilderLike + Sized { .expect("Failed to build dateOfBirth attribute") } + /// Create a documentDriversLicense attribute (sensitive) + /// + /// Creates a sensitive attribute for documentDriversLicense with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_document_drivers_license>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::DOCUMENT_DRIVERS_LICENSE) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build documentDriversLicense attribute") + } + + /// Create a documentIdCard attribute (sensitive) + /// + /// Creates a sensitive attribute for documentIdCard with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_document_id_card>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::DOCUMENT_ID_CARD) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build documentIdCard attribute") + } + + /// Create a documentPassport attribute (sensitive) + /// + /// Creates a sensitive attribute for documentPassport with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_document_passport>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::DOCUMENT_PASSPORT) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build documentPassport attribute") + } + + /// Create a documentPassportCard attribute (sensitive) + /// + /// Creates a sensitive attribute for documentPassportCard with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_document_passport_card>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::DOCUMENT_PASSPORT_CARD) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build documentPassportCard attribute") + } + + /// Create a documentPermit attribute (sensitive) + /// + /// Creates a sensitive attribute for documentPermit with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_document_permit>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::DOCUMENT_PERMIT) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build documentPermit attribute") + } + + /// Create a documentResidenceDocument attribute (sensitive) + /// + /// Creates a sensitive attribute for documentResidenceDocument with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_document_residence_document>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::DOCUMENT_RESIDENCE_DOCUMENT) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build documentResidenceDocument attribute") + } + + /// Create a documentVisa attribute (sensitive) + /// + /// Creates a sensitive attribute for documentVisa with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_document_visa>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::DOCUMENT_VISA) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build documentVisa attribute") + } + /// Create a email attribute (sensitive) /// /// Creates a sensitive attribute for email with the predefined OID. @@ -45,6 +157,22 @@ pub trait AttributeBuilderExtensions: AttributeBuilderLike + Sized { .expect("Failed to build email attribute") } + /// Create a firstName attribute (sensitive) + /// + /// Creates a sensitive attribute for firstName with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_first_name>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::FIRST_NAME) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build firstName attribute") + } + /// Create a fullName attribute (sensitive) /// /// Creates a sensitive attribute for fullName with the predefined OID. @@ -125,6 +253,54 @@ pub trait AttributeBuilderExtensions: AttributeBuilderLike + Sized { .expect("Failed to build jobTitle attribute") } + /// Create a lastName attribute (sensitive) + /// + /// Creates a sensitive attribute for lastName with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_last_name>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::LAST_NAME) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build lastName attribute") + } + + /// Create a middleName attribute (sensitive) + /// + /// Creates a sensitive attribute for middleName with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_middle_name>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::MIDDLE_NAME) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build middleName attribute") + } + + /// Create a nationality attribute (sensitive) + /// + /// Creates a sensitive attribute for nationality with the predefined OID. + /// + /// # Arguments + /// + /// - `value` - The attribute value as bytes + fn for_nationality>(value: V) -> Attribute { + Self::default() + .with_oid(oids::keeta::NATIONALITY) + .with_value(value) + .as_sensitive() + .build() + .expect("Failed to build nationality attribute") + } + /// Create a phoneNumber attribute (sensitive) /// /// Creates a sensitive attribute for phoneNumber with the predefined OID. @@ -156,6 +332,7 @@ pub trait AttributeBuilderExtensions: AttributeBuilderLike + Sized { .build() .expect("Failed to build postalCode attribute") } + } // Implement the extension trait for any type that implements AttributeBuilderLike @@ -178,14 +355,31 @@ mod tests { test_value: b"test_dateOfBirth_value", expected_sensitivity: true, }, - AttributeTestData { method_name: "for_email", test_value: b"test_email_value", expected_sensitivity: true }, + AttributeTestData { + method_name: "for_email", + test_value: b"test_email_value", + expected_sensitivity: true, + }, + AttributeTestData { + method_name: "for_first_name", + test_value: b"test_firstName_value", + expected_sensitivity: true, + }, AttributeTestData { method_name: "for_full_name", test_value: b"test_fullName_value", expected_sensitivity: true, }, - AttributeTestData { method_name: "for_id", test_value: b"test_id_value", expected_sensitivity: true }, - AttributeTestData { method_name: "for_issuer", test_value: b"test_issuer_value", expected_sensitivity: true }, + AttributeTestData { + method_name: "for_id", + test_value: b"test_id_value", + expected_sensitivity: true, + }, + AttributeTestData { + method_name: "for_issuer", + test_value: b"test_issuer_value", + expected_sensitivity: true, + }, AttributeTestData { method_name: "for_job_responsibility", test_value: b"test_jobResponsibility_value", @@ -196,6 +390,21 @@ mod tests { test_value: b"test_jobTitle_value", expected_sensitivity: true, }, + AttributeTestData { + method_name: "for_last_name", + test_value: b"test_lastName_value", + expected_sensitivity: true, + }, + AttributeTestData { + method_name: "for_middle_name", + test_value: b"test_middleName_value", + expected_sensitivity: true, + }, + AttributeTestData { + method_name: "for_nationality", + test_value: b"test_nationality_value", + expected_sensitivity: true, + }, AttributeTestData { method_name: "for_phone_number", test_value: b"test_phoneNumber_value", @@ -205,7 +414,7 @@ mod tests { method_name: "for_postal_code", test_value: b"test_postalCode_value", expected_sensitivity: false, - }, + } ]; #[test] @@ -215,39 +424,40 @@ mod tests { let result = match test_data.method_name { "for_date_of_birth" => AttributeBuilder::for_date_of_birth(test_data.test_value), "for_email" => AttributeBuilder::for_email(test_data.test_value), + "for_first_name" => AttributeBuilder::for_first_name(test_data.test_value), "for_full_name" => AttributeBuilder::for_full_name(test_data.test_value), "for_id" => AttributeBuilder::for_id(test_data.test_value), "for_issuer" => AttributeBuilder::for_issuer(test_data.test_value), "for_job_responsibility" => AttributeBuilder::for_job_responsibility(test_data.test_value), "for_job_title" => AttributeBuilder::for_job_title(test_data.test_value), + "for_last_name" => AttributeBuilder::for_last_name(test_data.test_value), + "for_middle_name" => AttributeBuilder::for_middle_name(test_data.test_value), + "for_nationality" => AttributeBuilder::for_nationality(test_data.test_value), "for_phone_number" => AttributeBuilder::for_phone_number(test_data.test_value), "for_postal_code" => AttributeBuilder::for_postal_code(test_data.test_value), _ => panic!("Unknown method: {}", test_data.method_name), }; // Verify the attribute was created correctly - assert_eq!( - result.is_sensitive(), - test_data.expected_sensitivity, - "Method {} should create {} attribute", - test_data.method_name, - if test_data.expected_sensitivity { - "sensitive" - } else { - "plain" - } + assert_eq!(result.is_sensitive(), test_data.expected_sensitivity, + "Method {} should create {} attribute", + test_data.method_name, + if test_data.expected_sensitivity { "sensitive" } else { "plain" } ); // Verify the value was set correctly let expected_value = test_data.test_value; let actual_value = result.as_ref(); - assert_eq!(actual_value, expected_value, "Method {} should set value correctly", test_data.method_name); + assert_eq!(actual_value, expected_value, + "Method {} should set value correctly", test_data.method_name + ); } } #[test] fn test_method_count() { // Ensure we have the expected number of testable methods (simple types only) - assert_eq!(ATTRIBUTE_TEST_DATA.len(), 9, "Expected 9 testable attribute methods"); + assert_eq!(ATTRIBUTE_TEST_DATA.len(), 13, + "Expected 13 testable attribute methods"); } } diff --git a/src/generated/from_impls.rs b/src/generated/from_impls.rs index 9c96272..4ff9ab1 100644 --- a/src/generated/from_impls.rs +++ b/src/generated/from_impls.rs @@ -1,4 +1,4 @@ -use crate::asn1::{error::AnchorAsn1Error, oids}; +use crate::asn1::{oids, error::AnchorAsn1Error}; use crate::generated::{Attribute, AttributeValue}; use keetanetwork_asn1::generated::iso20022::*; use rasn::types::OctetString; @@ -36,6 +36,17 @@ impl TryFrom for Attribute { } } +impl TryFrom for Attribute { + type Error = AnchorAsn1Error; + + fn try_from(value: Document) -> Result { + let name = oids::keeta::DOCUMENT; + let encoded = rasn::der::encode(&value)?; + let value = AttributeValue::sensitiveValue(OctetString::from_slice(&encoded)); + Ok(Attribute { name, value }) + } +} + impl TryFrom for Attribute { type Error = AnchorAsn1Error; @@ -46,3 +57,4 @@ impl TryFrom for Attribute { Ok(Attribute { name, value }) } } + diff --git a/src/testing.rs b/src/testing.rs index 62a3699..9fb6df2 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; -use keetanetwork_account::{Account, AccountError, Accountable, KeyPair, Keyable, Seed}; +use keetanetwork_account::{Account, AccountError, Accountable, KeyPair, Keyable}; use keetanetwork_asn1::SubjectPublicKeyInfo; use keetanetwork_crypto::prelude::IntoSecret; use keetanetwork_x509::SerialNumber; @@ -48,23 +48,26 @@ macro_rules! test_all_key_types { }; } -/// Helper function to create a test seed array. -pub fn create_test_seed_array() -> Seed { - let seed_bytes = hex::decode(TEST_SEED).unwrap(); - let seed_array: [u8; 32] = seed_bytes.try_into().unwrap(); - seed_array.into_secret() +/// Helper function to create an account from a hex seed string for different key types. +pub fn create_account_from_seed_hex(hex_seed: &str, index: u32) -> Account +where + T: KeyPair, + Account: TryFrom, Error = AccountError>, +{ + let seed_bytes = hex::decode(hex_seed).expect("Invalid hex seed"); + let seed_array: [u8; 32] = seed_bytes.try_into().expect("Seed must be 32 bytes"); + let seed = Keyable::Seed((seed_array.into_secret(), index)); + let accountable = Accountable::KeyAndType(seed, T::KEY_PAIR_TYPE); + Account::::try_from(accountable).expect("Failed to create account from seed") } -/// Helper function to create an account from seed for different key types. +/// Helper function to create an account from `TEST_SEED` for different key types. pub fn create_account_from_seed(index: u32) -> Account where T: KeyPair, Account: TryFrom, Error = AccountError>, { - let seed_array = create_test_seed_array(); - let seed = Keyable::Seed((seed_array, index)); - let accountable = Accountable::KeyAndType(seed, T::KEY_PAIR_TYPE); - Account::::try_from(accountable).unwrap() + create_account_from_seed_hex(TEST_SEED, index) } /// Helper function to create a public key only account (no private key). diff --git a/tests/common.rs b/tests/common.rs index 8d0df59..41a3a87 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -310,3 +310,9 @@ pub fn test_certificate_issued_by( Err("User certificate is not issued by the CA certificate".into()) } } + +/// Load a PEM fixture from the tests/fixtures directory +pub fn load_pem_fixture(name: &str) -> String { + let path = format!("tests/fixtures/{name}.pem"); + std::fs::read_to_string(&path).unwrap_or_else(|_| panic!("Failed to load fixture: {path}")) +} diff --git a/tests/fixtures/new_format.pem b/tests/fixtures/new_format.pem new file mode 100644 index 0000000..b77afa3 --- /dev/null +++ b/tests/fixtures/new_format.pem @@ -0,0 +1,90 @@ +-----BEGIN CERTIFICATE----- +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 +Ikz1g4z7LFI2Qw5gyPXl/+PcDSJwN6IHTQ== +-----END CERTIFICATE----- diff --git a/tests/fixtures/old_format.pem b/tests/fixtures/old_format.pem new file mode 100644 index 0000000..8309463 --- /dev/null +++ b/tests/fixtures/old_format.pem @@ -0,0 +1,100 @@ +-----BEGIN CERTIFICATE----- +MIISNTCCEdugAwIBAgIRAOaCUFRdyprwkaiMpdnlUOEwCwYJYIZIAWUDBAMKMFAx +TjBMBgNVBAMWRWtlZXRhX2FhYmptYnBmNW93YXV3dWxmM2kzZHM1dGZodWhodmdu +YXBodWhmM2xwNzVyeXFxYnVmdWxycjN5YmMzNjRueTAeFw0yNTEwMzAwMzU3MTFa +Fw0yNjEwMzAwMzU3MTFaMFAxTjBMBgNVBAMWRWtlZXRhX2FhYmptYnBmNW93YXV3 +dWxmM2kzZHM1dGZodWhodmduYXBodWhmM2xwNzVyeXFxYnVmdWxycjN5YmMzNjRu +eTA2MBAGByqGSM49AgEGBSuBBAAKAyIAApYF5eusClqLLtGxy7Mp6HPUzQPPQ5dr +f/scQgGhaLjHo4IQtjCCELIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AMYwHwYDVR0jBBgwFoAUhPqfjJhrW2Egd2bFpVPTbrSf12UwHQYDVR0OBBYEFIT6 +n4yYa1thIHdmxaVT0260n9dlMIIQTQYKKwYBBAGD6VMAAASCED0wghA5MIIBRQYK +KwYBBAGD6VMBAIGCATUwggExAgEAMIGtBglghkgBZQMEAS4EDAauPh5dc8su3w+o +DQSBkQTmY8I8+Sl0TuvRwaRK5hP4lJShAvlNfWWF1cqgNy4GmMt7BumYuCj0Qeb0 +OGa/bfTOksfOOVLaQ7/pQDaH5/rGHH4FNA9N+fu+u0JqigU6/mgE4rDaoN6PSSYe +EX8TRN9SZ4mZSqfPkK8n79XTIR/aS1pe/NryHo/OlHnUFFoH6wa6QWzO3dhZwlUW +SA7cQJMwXwQwlGrhJ8TNXLwCsHPWgUuMqbkAIYZSLk/Har1r2vvNszn4yV1Qiuq9 +L2AMgf5UTu3VBglghkgBZQMEAggEIP/EvIorXLRupGULgdGVCGRCNkzvVvusPwtQ +DPysFXNuBBv++SoBOplSnK3QMDsB554pXthS4rykRGa60YgwggFABgsrBgEEAYPp +UwEAAYGCAS8wggErAgEAMIGtBglghkgBZQMEAS4EDOCEDCt5UBZGbJSgOwSBkQTd +YUv23Sq1aOiw4UT3RFGhTFnp1stZ6M3YTN3/ZHSO+330dDIK6G7BE+6SQzyZThQv +AjcXn59VerJHlSYUTp5Ty3sqU2IHYi8eMKTWtjmlmUYjx0RA73D16CzYBDlRPogN +5TUrwqu+2kzJBhnXvzhFa07lLpYXUZTccSwKW16NPBU/nkfkyaHqM5TuOQxX9fQw +XwQwshHa98SmxqZsz+S2wmD8pO4Fy54N+yTA5njR7/VP+SyTQSJyY4z9qep7mdBf +CoXDBglghkgBZQMEAggEID0I5VTYFmzE4ReHX5P7OT2xJzoUZNwyDokrjCz1TA/U +BBWK2MrjS9JmZw+bHpW62xBX1rU0rQwwggFCBgsrBgEEAYPpUwEAAoGCATEwggEt +AgEAMIGtBglghkgBZQMEAS4EDL+gHXdvLknnCp3nhQSBkQRaVIWAQi3iB10H4ZeM +ENmvqdptllBtQIi9C1xPIbM55wnJy9RksipiJU5O0V3KHKkDwbYRcxOLAoeyxFA1 +YqXi03gOzqcjX4hDoTWPnfV95AVlnDeTW3QChn8fmlPBtcCa6D5J7Cmp+6pQbWIz +Q20kMsNHiXzjnQp4RhoWbIDDi40nUPM6+/mJ8CXcZvVYeIEwXwQw4x4N3vd1kZLo +f11gliHIj6El/oBm/iP9DFmDTQu2UGmm46T+78WX8ovBznAvwkt5BglghkgBZQME +AggEINNdE1YOlkwdKRiKLIwBZh7SO/HUNT7HYL+RXuEVor70BBfsMks4N20QGq+P +Fm9VBOlYidYYALNSUzCCAUkGCisGAQQBg+lTAQGBggE5MIIBNQIBADCBrQYJYIZI +AWUDBAEuBAxJA203OJmgXv3sFaoEgZEEKtGLSRppd3rzEMfn4kEjcPMO/HaacXcD +WyisW0PGKcFaxzh4gTy1VtCld5/nKNeXwRLD+hAl7+i+YMQDtIuZZeCfaB/OTBax +mhuSENaQ8QNw1G3pTGQ3bNEGegT784HTLBQE6zg6Ii4NtIpqoT90xgZGSxWWdF8F +3fPDGgAUFSB0O6ipmhB4hZbEkYIr+Zv7MF8EMFTf4nAbHixePjODMVf40363rpeX +oii5pcOuYs/tUD/Lau8Qt5FEcqXHk5pU+5UjnwYJYIZIAWUDBAIIBCBAu/xl8/vt +ovxj+hbzolK/DDGycmAjtct/hXDhTT1FUwQfel9//RHfveZXEKl7Eqk3Bf+E7wAY +DJSI+6Ln/c/gfjCCAUwGCisGAQQBg+lTAQOBggE8MIIBOAIBADCBrQYJYIZIAWUD +BAEuBAxDs0iDqgDdsLzUGI4EgZEE+dLvqg2joBhCCiC09xgjVgnOE318kTAU1zoO +tL2NR5P9weq8kUGTdaC84hxb+7wsaPSH6FZ9YB/6gBiQJLbDfhQtvcHsHjwqn/Pd +/0esW8wzPgHgm00oWN44/+nHBJ/Ts2s/3qb4EIwTSHYPZ5IufYUDGSo/GiMBiLC9 +UclKVBi7CDcSdamXdh34XWIPZ91dMF8EMBFuCKiy0A4NJYH4Ch11W2Knlf4zciih +8vi6/0iQ8nudV5/bPxMqqERpPtbQz9Vf0AYJYIZIAWUDBAIIBCBg0ZFApA0jkvzS +LTUEXaaDk4W7oZbGZ2PwLSIQcbCLQgQi6epTxvS3a7H1uoyfFpYMZysu0MwWufCT +UUDl4KL+Dco2IDCCAT4GCisGAQQBg+lTAQqBggEuMIIBKgIBADCBrQYJYIZIAWUD +BAEuBAy945disKI8g2o2K5MEgZEEZTde42LW+wL4iNwY+FqITjoPa0faVTm7VbGj +cmYsqzYm9s1yTytodENiF/srhE0bAQ1UvpTT9XrHtcu/uKl9cLhf1Mqui+AN5dSg +ynpVFQ7zG/f56lDHkeDouhcDYcSpiVJAy9YviOr+B8UMKRmkLm5zWPndutxC250x +lZpoHJTKH483DihdJHcktDH4UkZnMF8EMCQeNLGwoFcxNzbcAeG5NYV/5s0sclP8 +TBCayLZVDyKDsjKat8c2uzVBJXnw21OnvwYJYIZIAWUDBAIIBCD02Da0iMQJWfGc +GFukZcPUDIqeb+MxdLPGLmjnua3AhAQU/Kuyh1oPxBz3Id7p2shcoChFEVMwggF3 +BgorBgEEAYPpUwECgYIBZzCCAWMCAQAwga0GCWCGSAFlAwQBLgQMbL3rD62GOCoS +cT/8BIGRBJSThGZYiSt6YycYIO+SSJ66CRLEsrFK8RerMrfbubtV1sLL9QqNP6+7 +NZipNVpJ56w4Hqf4XV1dO2P9N97zbFpH6ZZ+NPMbVit3BDU5bvUDVOknjioCV8rM +aI6Y9277p0cnf5lBNi56Lzt0hSnqgtrz/pI+W6qYa/DpQVdgufeVQDuYwECuPhKV +upa1jMbfIjBfBDDwdRqSLZEB+NwRfIdSGe3ThNoaBgev7GMy4V8DE5Cm3IYFqc0G +1NbfGQlHIgZ+u4cGCWCGSAFlAwQCCAQgItNiCTEt//93Hin4oWEBTSKOCyIr2w25 +Kg0515ZnwJ8ETSsbUSVZzRpfb3BazcjExcgZ6lDXEI1oUbzEUUB9hNDQpbarL8Wl +fDlevABvjw2UV8CwJOMCX5R4WoAWGQtKwMhXuEw0/liz11DfKnqpMIIENAYLKwYB +BAGD6VMBCwCBggQjMIIEHwIBADCBrQYJYIZIAWUDBAEuBAzOuhwViFaOpTEMWnAE +gZEE2LbTU6PmYVlxDdfoRuh150x5qjgTu+uRbQfLS3EOmG0G5wDkuAtccXywj2rY +uzNY+OlRBeUKOMb4NP1ZeECW3H3UuoMUVkO4+0ZlNxaqa036IgC7KBp47WgS15wp +9a7ihz8dRY0uH0H71uFR2U2URiucmcr5ILSTDRKqZXS5/Yh26Fk+QGaL8EGsqp7I +DMiOMF8EMFGtKHxBY32PFhmoZtwhUJD8Pw/cW2gxd4vH3dBvu32mP4tAEjdYzu39 +5O759ICPIAYJYIZIAWUDBAIIBCCBQ9hR6eUYQ1RFbHS+8OJaJdtmeb+oX7iGDxTG +wGUxOASCAwf8Fs92ie9hajw295KDH61Ah75p6E2QS3Fqot45tPG6onzbIGPX+LZJ +ycmTY1MllASYMnBmVfbUYU8LvLUSQPmJ6M0J1yvgugNK+Mr/dtxyNrtq6IHo+mJ1 +7wDjSxDyBpLDAcxZG9KmGgtiYw/gYSZoXt3fkQajZ/v5NYIDKInAeQIVfvbxVFVL +m+XyzKY7HLbLD7JhosQdTtJYFjYwxzbdLXr4ojz9OwNGgwtQq/3E4cZl91KwAkgK +wze1WSw6KAVb65gDyt9XC/+q3GAOEwACTk2IhvMQYBNjS80Y8cwFGLBxLysaZBF7 +CGDqK9SQZr/XiCPBPotrym7L6X8SwYBX+zlIxQYyg72SNU4nYjIjqIzoaLYILNZT +UieMlXmIBJm9cB9bA1VbugX04akEKzS0OjEP7Njd8a7RZu2qHqULWNmjb2R3u7Ee +s8v0vCuyzkylc3Ew7Ui88Gms4jiUvAKQ7xkP/bqJD1P8BvUtjHSPdldgJc9W4TXq ++Ka/zh9F4FhSnEMcmw3FyAU21ubF5SrbfSGHN6nHw6xHIZ00cdKd+7zEK2+3qhhj +I2EDCGEwH+SSnXfyJnuGgu2Ij1qAVIYsC7h6YfHJztQUIVeJLW5C7tuH+SaL1n9X +hL8vejgob7T9nVKKrqcZ4Ojziwb0GBpx3HJ6D44X5BjJ+iJJD9sg8IHw8WkmfSaA +Fem895JNqt8PvBCWpwL825GR8M45rvHogi+dp2WFWQcIi/mrlwprjhyMYm7cK22J +lExRweXX6eITs6b2+tzB3StzuOyEqrUD7r7eVCsJKrres7xofbsL1oM49GMT8ayL +LC2h7q+0H/VOQVVywBAJUWgbHP3Cnd8scbmLjmheLbBIcCAp9gScZQFY3xNGjNQH +ldRWiMLiVG8m65Pw+L5IMMAvrzg3eywS8vI/EVPS1V/jqSlMhMhCJSI9A7IE6pgV +v+291VYgRUuv3c4krj1jZ8x6g63LiOg4lZ+I1k/UCNs4fPaBrmSOqEUDPevPx5+T +EXAnGkRY+W7rMuiDoywkMIIBhAYKKwYBBAGD6VMBCIGCAXQwggFwAgEAMIGtBglg +hkgBZQMEAS4EDMO72R3ZCFILoqv/qgSBkQRJYrZ54HCQKK4gxZv0/Q5CM0PJOfIV +OtvXP2FA3IeTHidExafZH1gXYvewxIGsRp1/kGPxLe3zx+oE2V3n6X67Z4Oqtnho +3TYkrGShtNY1Hcv4f1wpeqSFt0O+89RRziMJi8PGFTtDdhs4AA4ya7ANsfL4Hyye +8tyvT2liFDW19ntnYkO0J3AThGeVPc7/TYIwXwQweOXUFOVHlroVCU8gUiF0vRmq +sqXBO1+L0gT2KogBV+d2ozwRq8hHw8fQktfkggVZBglghkgBZQMEAggEIGk/is4H +gN3Ecu3Jt0XZt57Q/8vOoJd1goqNToA5hhYABFohiZgM9ibrlEFnqTBHF5OVTrS8 +g/gsbN79BC0IZ+C1QZzTq2OeNMZb9nEeFh9KL5EgcPTB2kds0KOxMQ/EtZRxKqGF +G+zMrPkjZJoIJIFJ3OLhZNpsE78sk2owggFIBgorBgEEAYPpUwEEgYIBODCCATQC +AQAwga0GCWCGSAFlAwQBLgQM77L/4QFlHX12Ymx+BIGRBPQKZ7v5vpL0Bd84Qxup +5BTTN8o53yOEg6WNJretP2I701szh7iON6zmSMxnq6htw3Uofxm6LJ93c59bhsbU +7eR6IuA/n97bZbck/fQDJg+1agNjF2xNB5yqgyIo4JENVMyU1v7TvoGicxZnLJRg +70DeopLBmXdjXN20YQfXj+hsaP99xlt2dcekP/vBoq8E7DBfBDD+PNkXNtN5LU37 +PDAPVn0HYTFIZRQx+MQ0Wj9o2bdFsH8tqon5pQMOHzJP9TLR0ucGCWCGSAFlAwQC +CAQg/9s/LHXCQnvBwcuMkkEnSCzkjXU6euuYAht+XSZgsTwEHtKFUV8gBXMuSG82 +VNUJJMfiGw6G9cF+96K/otwDHTALBglghkgBZQMEAwoDRwAwRAIgCGIfyxdQwjmr +vGVSkpNM+BsmzDWDarsjpw/2ZFDvSCUCIBcBGiZPCsgPvbRKEDGStAkJYngZ7Zh3 +4Qh9l2aV4VzD +-----END CERTIFICATE----- diff --git a/tests/typescript_interoperability.rs b/tests/typescript_interoperability.rs new file mode 100644 index 0000000..e742459 --- /dev/null +++ b/tests/typescript_interoperability.rs @@ -0,0 +1,142 @@ +//! TypeScript Interoperability Tests +//! +//! This module tests that Rust can parse and process certificates generated +//! by the TypeScript implementation, ensuring cross-platform compatibility. + +mod common; + +use core::str::FromStr; + +use keetanetwork_account::KeyECDSASECP256K1; +use keetanetwork_anchor::certificates::Certificate; +use keetanetwork_anchor::testing::create_account_from_seed_hex; +use keetanetwork_x509::certificates::Certificate as X509Certificate; + +use common::load_pem_fixture; + +/// Test case for TypeScript certificate interoperability +struct InteropTestCase { + name: &'static str, + pem_fixture: &'static str, + seed: &'static str, + expected_attributes: &'static [&'static str], +} + +/// Test cases from the TypeScript test suite (certificates.test.ts) +/// +/// Note: The expected_attributes list contains attributes that MUST be present. +/// Both old and new format certificates are expected to have these core attributes. +const TEST_CASES: &[InteropTestCase] = &[ + InteropTestCase { + name: "old format (no context tags)", + pem_fixture: "old_format", + seed: "38D8765C39247A2ED61C8C277DEB9E1E82D93DF2BB7C642EF86C63F5FE07F83D", + // Core attributes that must be present in old format certificates + expected_attributes: &["entityType", "fullName", "email", "dateOfBirth", "documentDriversLicense"], + }, + InteropTestCase { + name: "new format (with context tags)", + pem_fixture: "new_format", + seed: "657340915c5dd7d4610feaf281f6f4658b5689d414710ce080eb8f8b0b2e03a9", + // Core attributes that must be present in new format certificates + expected_attributes: &["entityType", "fullName", "email", "dateOfBirth", "documentDriversLicense"], + }, +]; + +/// Parse a certificate from PEM fixture and wrap it in our Certificate type +fn parse_certificate_from_fixture(fixture_name: &str) -> Certificate { + let pem_content = load_pem_fixture(fixture_name); + let x509_cert = X509Certificate::from_str(&pem_content).expect("Failed to parse PEM certificate"); + Certificate::new(x509_cert) +} + +#[test] +fn test_typescript_certificate_parsing() { + for test_case in TEST_CASES { + let certificate = parse_certificate_from_fixture(test_case.pem_fixture); + assert!(certificate.has_kyc_attributes(), "{}: Certificate should have KYC attributes", test_case.name); + assert!( + certificate.kyc_attribute_count() >= test_case.expected_attributes.len(), + "{}: Certificate should have at least {} KYC attributes, found {}", + test_case.name, + test_case.expected_attributes.len(), + certificate.kyc_attribute_count() + ); + } +} + +#[test] +fn test_typescript_certificate_attribute_decryption() { + for test_case in TEST_CASES { + let certificate = parse_certificate_from_fixture(test_case.pem_fixture); + let account = create_account_from_seed_hex::(test_case.seed, 0); + + // Try to decrypt each expected sensitive attribute + for attr_name in test_case.expected_attributes { + let attr = certificate + .get_kyc_attribute(attr_name) + .expect(&format!("{}: Attribute '{}' should exist", test_case.name, attr_name)); + + if attr.is_sensitive() { + let result = certificate.decrypt_kyc_attribute(attr_name, &account.keypair); + assert!( + result.is_ok(), + "{}: Failed to decrypt sensitive attribute '{}': {:?}", + test_case.name, + attr_name, + result.err() + ); + + let decrypted = result.expect("Decryption should succeed"); + assert!( + !decrypted.is_empty(), + "{}: Decrypted attribute '{}' should not be empty", + test_case.name, + attr_name + ); + } + } + } +} + +#[test] +fn test_typescript_certificate_wrong_key_fails() { + for test_case in TEST_CASES { + let certificate = parse_certificate_from_fixture(test_case.pem_fixture); + // Create account with wrong seed (index 99 instead of 0) + let wrong_account = create_account_from_seed_hex::(test_case.seed, 99); + + // Find a sensitive attribute to test with + for attr_name in test_case.expected_attributes { + let attr = certificate.get_kyc_attribute(attr_name); + if let Some(attr) = attr { + if attr.is_sensitive() { + let result = certificate.decrypt_kyc_attribute(attr_name, &wrong_account.keypair); + assert!( + result.is_err(), + "{}: Decryption with wrong key should fail for attribute '{}'", + test_case.name, + attr_name + ); + } + } + } + } +} + +#[test] +fn test_typescript_certificate_subject_contains_keeta() { + for test_case in TEST_CASES { + let certificate = parse_certificate_from_fixture(test_case.pem_fixture); + let x509 = certificate.to_x509(); + + // Get subject DN as string + let subject = x509.tbs_certificate.subject.to_string(); + assert!( + subject.to_lowercase().contains("keeta_"), + "{}: Certificate subject should contain 'keeta_', got: {}", + test_case.name, + subject + ); + } +}