diff --git a/openssl-sys/CHANGELOG.md b/openssl-sys/CHANGELOG.md index 641f0d4b7..7fa7302b0 100644 --- a/openssl-sys/CHANGELOG.md +++ b/openssl-sys/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Added + +* Added `NAME_CONSTRAINTS`, `GENERAL_SUBTREE`, `stack_st_GENERAL_SUBTREE` +* Added `POLICY_MAPPING`, `stack_st_POLICY_MAPPING` + ## [v0.9.104] - 2024-10-15 ### Added diff --git a/openssl-sys/src/handwritten/x509v3.rs b/openssl-sys/src/handwritten/x509v3.rs index 1a548c0e2..df6d98f28 100644 --- a/openssl-sys/src/handwritten/x509v3.rs +++ b/openssl-sys/src/handwritten/x509v3.rs @@ -165,3 +165,39 @@ extern "C" { pub fn X509_check_ip(x: *mut X509, chk: *const c_uchar, chklen: usize, flags: c_uint) -> c_int; pub fn X509_check_ip_asc(x: *mut X509, ipasc: *const c_char, flags: c_uint) -> c_int; } + +#[repr(C)] +pub struct GENERAL_SUBTREE { + pub base: *mut GENERAL_NAME, + pub minimum: *mut ASN1_INTEGER, + pub maximum: *mut ASN1_INTEGER, +} + +extern "C" { + pub fn GENERAL_SUBTREE_new() -> *mut GENERAL_SUBTREE; + pub fn GENERAL_SUBTREE_free(name: *mut GENERAL_SUBTREE); +} + +stack!(stack_st_GENERAL_SUBTREE); + +#[repr(C)] +pub struct NAME_CONSTRAINTS { + pub permittedSubtrees: *mut stack_st_GENERAL_SUBTREE, + pub excludedSubtrees: *mut stack_st_GENERAL_SUBTREE, +} + +extern "C" { + pub fn NAME_CONSTRAINTS_free(nc: *mut NAME_CONSTRAINTS); +} + +#[repr(C)] +pub struct POLICY_MAPPING { + pub issuerDomainPolicy: *mut ASN1_OBJECT, + pub subjectDomainPolicy: *mut ASN1_OBJECT, +} + +extern "C" { + pub fn POLICY_MAPPING_free(nc: *mut POLICY_MAPPING); +} + +stack!(stack_st_POLICY_MAPPING); diff --git a/openssl/CHANGELOG.md b/openssl/CHANGELOG.md index e939d4784..02a024297 100644 --- a/openssl/CHANGELOG.md +++ b/openssl/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Added + +- Added `X509Ref::name_constraints` +- Added `X509Ref::policy_mappings` + ## [v0.10.68] - 2024-10-16 ### Fixed diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index 67c86ee3d..3cdc30ad4 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -477,6 +477,38 @@ impl X509Ref { } } + /// Returns this certificate's [`name constraints`] entry, if it exists. + /// + /// [`name constraints`]: https://tools.ietf.org/html/rfc5280#section-4.2.1.10 + #[corresponds(X509_get_ext_d2i)] + pub fn name_constraints(&self) -> Option<&NameConstraintsRef> { + unsafe { + let ext = ffi::X509_get_ext_d2i( + self.as_ptr(), + ffi::NID_name_constraints, + ptr::null_mut(), + ptr::null_mut(), + ); + NameConstraintsRef::from_const_ptr_opt(ext as *mut _) + } + } + + /// Returns this certificate's [`policy mappings`] entries, if they exist. + /// + /// [`policy mappings`]: https://tools.ietf.org/html/rfc5280#section-4.2.1.5 + #[corresponds(X509_get_ext_d2i)] + pub fn policy_mappings(&self) -> Option> { + unsafe { + let stack = ffi::X509_get_ext_d2i( + self.as_ptr(), + ffi::NID_policy_mappings, + ptr::null_mut(), + ptr::null_mut(), + ); + Stack::from_ptr_opt(stack as *mut _) + } + } + /// Retrieves the path length extension from a certificate, if it exists. #[corresponds(X509_get_pathlen)] #[cfg(any(ossl110, boringssl))] @@ -2261,6 +2293,78 @@ impl Stackable for AccessDescription { type StackType = ffi::stack_st_ACCESS_DESCRIPTION; } +foreign_type_and_impl_send_sync! { + type CType = ffi::GENERAL_SUBTREE; + fn drop = ffi::GENERAL_SUBTREE_free; + + /// An `X509` subtree. + pub struct GeneralSubtree; + /// Reference to `GeneralSubtree`. + pub struct GeneralSubtreeRef; +} + +impl Stackable for GeneralSubtree { + type StackType = ffi::stack_st_GENERAL_SUBTREE; +} + +impl GeneralSubtreeRef { + pub fn base(&self) -> &GeneralNameRef { + unsafe { GeneralNameRef::from_ptr((*self.as_ptr()).base) } + } + + pub fn minimum(&self) -> Option<&Asn1IntegerRef> { + unsafe { Asn1IntegerRef::from_const_ptr_opt((*self.as_ptr()).minimum) } + } + + pub fn maximum(&self) -> Option<&Asn1IntegerRef> { + unsafe { Asn1IntegerRef::from_const_ptr_opt((*self.as_ptr()).maximum) } + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::NAME_CONSTRAINTS; + fn drop = ffi::NAME_CONSTRAINTS_free; + + /// `NameConstraints` of certificate authority information. + pub struct NameConstraints; + /// Reference to `NameConstraints`. + pub struct NameConstraintsRef; +} + +impl NameConstraintsRef { + pub fn permitted(&self) -> Option<&StackRef> { + unsafe { StackRef::from_const_ptr_opt((*self.as_ptr()).permittedSubtrees) } + } + + pub fn excluded(&self) -> Option<&StackRef> { + unsafe { StackRef::from_const_ptr_opt((*self.as_ptr()).excludedSubtrees) } + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::POLICY_MAPPING; + fn drop = ffi::POLICY_MAPPING_free; + + /// `PolicyMapping` of certificate authority information. + pub struct PolicyMapping; + /// Reference to `PolicyMapping`. + pub struct PolicyMappingRef; +} + +impl Stackable for PolicyMapping { + type StackType = ffi::stack_st_POLICY_MAPPING; +} + +impl PolicyMappingRef { + pub fn issuer_domain_policy(&self) -> &Asn1ObjectRef { + unsafe { Asn1ObjectRef::from_ptr((*self.as_ptr()).issuerDomainPolicy) } + } + + pub fn subject_domain_policy(&self) -> &Asn1ObjectRef { + unsafe { Asn1ObjectRef::from_ptr((*self.as_ptr()).subjectDomainPolicy) } + } +} + foreign_type_and_impl_send_sync! { type CType = ffi::X509_ALGOR; fn drop = ffi::X509_ALGOR_free; diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index 25c2da012..8db2046e2 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -265,6 +265,56 @@ fn test_subject_alt_name_iter() { assert!(subject_alt_names_iter.next().is_none()); } +#[test] +fn test_name_constraints() { + let cert = include_bytes!("../../test/name_constraints.pem"); + let cert = X509::from_pem(cert).unwrap(); + + // Access through .name_constraints() + let name_constraints = cert + .name_constraints() + .expect("Name constraints extension should be present"); + + let permitted = name_constraints.permitted().unwrap(); + assert_eq!(permitted.len(), 1); + + let base = permitted[0].base().dnsname().unwrap(); + assert_eq!(base, ".example.com"); + + let minimum = permitted[0].minimum().is_none(); + assert!(minimum); + let maximum = permitted[0].maximum().is_none(); + assert!(maximum); + + let excluded = name_constraints.excluded().unwrap(); + assert_eq!(excluded.len(), 1); + + let base = excluded[0].base().dnsname().unwrap(); + assert_eq!(base, ".example.net"); + + let minimum = excluded[0].minimum().is_none(); + assert!(minimum); + let maximum = excluded[0].maximum().is_none(); + assert!(maximum); +} + +#[test] +fn test_policy_mappings() { + let cert = include_bytes!("../../test/policy_mappings.pem"); + let cert = X509::from_pem(cert).unwrap(); + + // Access through .policy_mappings() + let policy_mappings = cert + .policy_mappings() + .expect("Policy mappings should be present"); + + assert_eq!(policy_mappings.len(), 1); + let policy = policy_mappings[0].issuer_domain_policy().to_string(); + assert_eq!(policy, "2.16.840.1.101.3.2.1.48.1"); + let policy = policy_mappings[0].subject_domain_policy().to_string(); + assert_eq!(policy, "2.16.840.1.101.3.2.1.48.2"); +} + #[test] fn test_aia_ca_issuer() { // With AIA diff --git a/openssl/test/name_constraints.pem b/openssl/test/name_constraints.pem new file mode 100644 index 000000000..f2b66764e --- /dev/null +++ b/openssl/test/name_constraints.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIUOwEq2g8zyCoGLVhxTQ/loZ7M0qwwDQYJKoZIhvcNAQEL +BQAwLDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0V4YW1wbGUxCzAJBgNVBAsMAkNB +MB4XDTI0MTAxNjA0NTcyOFoXDTI1MTAxNjA0NTcyOFowLDELMAkGA1UEBhMCVVMx +EDAOBgNVBAoMB0V4YW1wbGUxCzAJBgNVBAsMAkNBMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAoRQKVqSwPHyHQiHxMVCxabVO2W7dNGSP7jvnAR5Yu424 +9vLaY/n347f80w1ad8c1Ecl8m8vrprg7jAXuDoI02ys62J6qE87bqGpfL5oJ5AGD +RDm3x+T63bNvBbU2pSN5F0INXlahaGufE1BqDQlPq3CRDDnXSslTMGruZbcriW/u +IMgvnCGm7WtKejcnWa0vEmOo9KgOnHrpzYqWRJg6FmeT8D4P703HSexL1wNw7e4n +jSmaWrAMQeq8H0srjyMfKItMm1khYN7s4xFHkYARh9ppolXAXwLN56c9ZTunpb+p +Y1yBpYIeOp8NIDvwTfdUyHzr6ZsQ4tHu3XsXi4HMmBqnXxDIIctkwE1zB8nrGF8J +8GEH0hwXInsIG8ib7FIpF3T9zx85FsjK90TPMIG5DoBg3IuYiGU4Jc3tKrQLbrGl +fr25tneqbvndLSsIrRkb5z7JNnLNTv6eCVQGus6MYaOnR/tJAm7htCoImxvuc7+h +4nXIzypjNz1ephlvtad69RZYf1WQMMeVR3uK6T/J6QYVN3VqmU62pRJPuC3J3x/l +nEqJphA51gattgX7WJLb0dKSEEuRZzSmEJcgEnHg3Pyg0ST7Tbbj3hkLYVLD+kOn +VTaI4PDzLNSWnucZ6hcIDJr3rTj4wekkq+MsZ2dOacrzZKlJKWXTQGtdSeTxIesC +AwEAAaN0MHIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFGa7OPpxvHJn7JmdrnSvM8Yc1plJMDAGA1UdHgEB/wQmMCSgEDAOggwuZXhh +bXBsZS5jb22hEDAOggwuZXhhbXBsZS5uZXQwDQYJKoZIhvcNAQELBQADggIBAAbd +/7jTjmctPZrCB2//jLQXzPkqtwjK/kIWJ90l2XtlALsykdjawGi7GsbLHWabM0Cm +55iRFsQ+w3DC9+m/2wCL6yG/Pjnj/c3PNb707S4y57o8ALL7YuURso3PGOqYfBY1 +G851MXahFCM3SjxVKlJc6XCfs/XgJ7N0loAYLQSNsYjNk4QiuEsU6r7WtPy0qEzv +ly6u8U8DWQ0EEH3dA9UzUpwHW3sipUzxeKpxzqhaTc8U8+TPS+InEJfWHrGMKwF7 +Db1q7aQA6s8U7uLG45RIIvLBerpdyPsSpcFIlP7DAhllPgTWhtkAAvcddkeIUUcy +YKsjKMrtIX9Jzzo+ddgsIB8C/SRSE310lx2EJoD8gS1zMg4y9zQ/WXLI2ASzsO42 +n4b/O7me1Qd85ku8XgnEPxmLIzpNgfDTPR5z85zeCcejmFEXzXCAwIKhCub9tjjY +9ujbrI1jGGSfCv2YvxwfZgbMMwii5YA8wK9aXqqndV/IZasxDZqJiXhDhzxZMscf +RCOphZL2dzjeiS0iWmlSTr8YQ0jr5kjZc/zl5Z3WEQFWcVzBAjJwcYiiXtIWQBVH +aEdupCi8xLVm5lRZIGL/UupM2Kz8gPPxUJkmKGDS/cceoUmtdXsNrlNUSHiXlWh1 +ZITcIQozA0r0o+aEXoQtc6Tmb/rNnf84Rrfns622 +-----END CERTIFICATE----- diff --git a/openssl/test/policy_mappings.pem b/openssl/test/policy_mappings.pem new file mode 100644 index 000000000..b6b5e5e92 --- /dev/null +++ b/openssl/test/policy_mappings.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICfDCCAiOgAwIBAgIKaALI6NVFszY7ADAKBggqhkjOPQQDAjBcMQswCQYDVQQG +EwJVUzEgMB4GA1UECgwXVGVzdCBNQ0EgT3JnYW5pemF0aW9uIDIxHzAdBgNVBAMM +FlRlc3QgSUVFRSAyMDMwLjUgTUNBIDIxCjAIBgNVBAUMATEwIBcNMTkwNDE2MDU0 +MzE4WhgPOTk5OTEyMzEyMzU5NTlaMF4xCzAJBgNVBAYTAlVTMSEwHwYDVQQKDBhU +ZXN0IE1JQ0EgT3JnYW5pemF0aW9uIDIxIDAeBgNVBAMMF1Rlc3QgSUVFRSAyMDMw +LjUgTUlDQSAyMQowCAYDVQQFDAE0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +hfu4ivOW15lzRrkguuUpDi4jR82g+p1z/2m0AJmiC4uJ2nY4c/80EDhG9mzuOaW4 +UqJ5xSoyJWqmjDqLi7RdOaOByDCBxTASBgNVHRMBAf8ECDAGAQH/AgEAMBEGA1Ud +DgQKBAhD4TrR7KbbDTATBgNVHSMEDDAKgAhO1GdTUZdzwDAOBgNVHQ8BAf8EBAMC +AQYwUgYDVR0gAQH/BEgwRjAMBgorBQEEAYK+HAEBMAwGCisFAQQBgr4cAQIwDAYK +KwUBBAGCvhwBAzAMBgorBQEEAYK+HAIBMAwGCisFAQQBgr4cAgQwIwYDVR0hBBww +GjAYBgpghkgBZQMCATABBgpghkgBZQMCATACMAoGCCqGSM49BAMCA0cAMEQCIDJn +3s1sy27lQgxYNPImr9nYzsvIlTrEGbZUuBcw7knuAiAGWfpCVFxzE+UujZ8+ylqT +k6IKlu1bUvsNJL0Tv8XV6A== +-----END CERTIFICATE-----