1use crate::bio::{MemBio, MemBioBuf};
15use crate::error::ErrorStack;
16use crate::obj::Oid;
17use native_ossl_sys as sys;
18use std::ffi::CStr;
19use std::marker::PhantomData;
20use std::sync::Arc;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct BrokenDownTime {
31 pub year: i32,
33 pub month: u8,
35 pub day: u8,
37 pub hour: u8,
39 pub minute: u8,
41 pub second: u8,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct SignatureInfo {
52 pub md_nid: i32,
57 pub pk_nid: i32,
59 pub security_bits: i32,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct KeyUsage(pub u32);
73
74impl KeyUsage {
75 pub const DIGITAL_SIGNATURE: Self = Self(0x0080);
77 pub const NON_REPUDIATION: Self = Self(0x0040);
79 pub const KEY_ENCIPHERMENT: Self = Self(0x0020);
81 pub const DATA_ENCIPHERMENT: Self = Self(0x0010);
83 pub const KEY_AGREEMENT: Self = Self(0x0008);
85 pub const KEY_CERT_SIGN: Self = Self(0x0004);
87 pub const CRL_SIGN: Self = Self(0x0002);
89 pub const ENCIPHER_ONLY: Self = Self(0x0001);
91 pub const DECIPHER_ONLY: Self = Self(0x8000);
93
94 #[must_use]
96 pub fn contains(self, flag: Self) -> bool {
97 self.0 & flag.0 == flag.0
98 }
99
100 #[must_use]
102 pub fn bits(self) -> u32 {
103 self.0
104 }
105}
106
107impl std::ops::BitOr for KeyUsage {
108 type Output = Self;
109 fn bitor(self, rhs: Self) -> Self {
110 Self(self.0 | rhs.0)
111 }
112}
113
114impl std::ops::BitAnd for KeyUsage {
115 type Output = Self;
116 fn bitand(self, rhs: Self) -> Self {
117 Self(self.0 & rhs.0)
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
129pub enum GeneralName {
130 Dns(String),
132 Email(String),
134 Uri(String),
136 IpAddress(Vec<u8>),
138 RegisteredId(Oid),
140 OtherName {
146 type_id: Oid,
148 value: Vec<u8>,
150 },
151}
152
153pub struct X509 {
159 ptr: *mut sys::X509,
160}
161
162unsafe impl Send for X509 {}
164unsafe impl Sync for X509 {}
165
166impl Clone for X509 {
167 fn clone(&self) -> Self {
168 unsafe { sys::X509_up_ref(self.ptr) };
169 X509 { ptr: self.ptr }
170 }
171}
172
173impl Drop for X509 {
174 fn drop(&mut self) {
175 unsafe { sys::X509_free(self.ptr) };
176 }
177}
178
179impl X509 {
180 pub(crate) unsafe fn from_ptr(ptr: *mut sys::X509) -> Self {
186 X509 { ptr }
187 }
188
189 pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
193 let bio = MemBioBuf::new(pem)?;
194 let ptr = unsafe {
195 sys::PEM_read_bio_X509(
196 bio.as_ptr(),
197 std::ptr::null_mut(),
198 None,
199 std::ptr::null_mut(),
200 )
201 };
202 if ptr.is_null() {
203 return Err(ErrorStack::drain());
204 }
205 Ok(unsafe { Self::from_ptr(ptr) })
206 }
207
208 pub fn from_pem_in(_ctx: &Arc<crate::lib_ctx::LibCtx>, pem: &[u8]) -> Result<Self, ErrorStack> {
219 Self::from_pem(pem)
220 }
221
222 pub fn new_in(ctx: &Arc<crate::lib_ctx::LibCtx>) -> Result<Self, ErrorStack> {
233 let ptr = unsafe { sys::X509_new_ex(ctx.as_ptr(), std::ptr::null()) };
237 if ptr.is_null() {
238 return Err(ErrorStack::drain());
239 }
240 Ok(unsafe { Self::from_ptr(ptr) })
241 }
242
243 pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
251 let mut der_ptr = der.as_ptr();
252 let len = i64::try_from(der.len()).map_err(|_| ErrorStack::drain())?;
253 let ptr =
254 unsafe { sys::d2i_X509(std::ptr::null_mut(), std::ptr::addr_of_mut!(der_ptr), len) };
255 if ptr.is_null() {
256 return Err(ErrorStack::drain());
257 }
258 Ok(unsafe { Self::from_ptr(ptr) })
259 }
260
261 pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
265 let mut bio = MemBio::new()?;
266 crate::ossl_call!(sys::PEM_write_bio_X509(bio.as_ptr(), self.ptr))?;
267 Ok(bio.into_vec())
268 }
269
270 pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
277 let len = unsafe { sys::i2d_X509(self.ptr, std::ptr::null_mut()) };
278 if len < 0 {
279 return Err(ErrorStack::drain());
280 }
281 let mut buf = vec![0u8; usize::try_from(len).unwrap_or(0)];
282 let mut out_ptr = buf.as_mut_ptr();
283 let written = unsafe { sys::i2d_X509(self.ptr, std::ptr::addr_of_mut!(out_ptr)) };
284 if written < 0 {
285 return Err(ErrorStack::drain());
286 }
287 buf.truncate(usize::try_from(written).unwrap_or(0));
288 Ok(buf)
289 }
290
291 #[must_use]
293 pub fn subject_name(&self) -> X509Name<'_> {
294 let ptr = unsafe { sys::X509_get_subject_name(self.ptr) }.cast();
298 X509Name {
299 ptr,
300 _owner: PhantomData,
301 }
302 }
303
304 #[must_use]
306 pub fn issuer_name(&self) -> X509Name<'_> {
307 let ptr = unsafe { sys::X509_get_issuer_name(self.ptr) }.cast();
308 X509Name {
309 ptr,
310 _owner: PhantomData,
311 }
312 }
313
314 #[must_use]
318 pub fn serial_number(&self) -> Option<i64> {
319 let ai = unsafe { sys::X509_get0_serialNumber(self.ptr) };
320 if ai.is_null() {
321 return None;
322 }
323 let mut n: i64 = 0;
324 let rc = unsafe { sys::ASN1_INTEGER_get_int64(std::ptr::addr_of_mut!(n), ai) };
325 if rc == 1 {
326 Some(n)
327 } else {
328 None
329 }
330 }
331
332 #[must_use]
340 pub fn serial_number_bytes(&self) -> Option<Vec<u8>> {
341 let ai = unsafe { sys::X509_get_serialNumber(self.ptr) };
347 if ai.is_null() {
348 return None;
349 }
350 Some(unsafe { asn1_string_data(ai.cast()) }.to_vec())
354 }
355
356 #[must_use]
361 pub fn not_before_str(&self) -> Option<String> {
362 let t = unsafe { sys::X509_get0_notBefore(self.ptr) };
363 asn1_time_to_str(t)
364 }
365
366 #[must_use]
370 pub fn not_after_str(&self) -> Option<String> {
371 let t = unsafe { sys::X509_get0_notAfter(self.ptr) };
372 asn1_time_to_str(t)
373 }
374
375 #[must_use]
379 pub fn not_before_tm(&self) -> Option<BrokenDownTime> {
380 let t = unsafe { sys::X509_get0_notBefore(self.ptr) };
385 asn1_time_to_broken_down(t)
386 }
387
388 #[must_use]
392 pub fn not_after_tm(&self) -> Option<BrokenDownTime> {
393 let t = unsafe { sys::X509_get0_notAfter(self.ptr) };
395 asn1_time_to_broken_down(t)
396 }
397
398 #[must_use]
400 pub fn is_valid_now(&self) -> bool {
401 let nb = unsafe { sys::X509_get0_notBefore(self.ptr) };
402 let na = unsafe { sys::X509_get0_notAfter(self.ptr) };
403 unsafe {
405 sys::X509_cmp_time(nb, std::ptr::null_mut()) <= 0
406 && sys::X509_cmp_time(na, std::ptr::null_mut()) >= 0
407 }
408 }
409
410 pub fn public_key(&self) -> Result<crate::pkey::Pkey<crate::pkey::Public>, ErrorStack> {
416 let ptr = unsafe { sys::X509_get_pubkey(self.ptr) };
417 if ptr.is_null() {
418 return Err(ErrorStack::drain());
419 }
420 Ok(unsafe { crate::pkey::Pkey::from_ptr(ptr) })
421 }
422
423 #[must_use]
431 pub fn public_key_is_a(&self, alg: &CStr) -> bool {
432 let pkey = unsafe { sys::X509_get0_pubkey(self.ptr) };
438 if pkey.is_null() {
439 return false;
440 }
441 unsafe { sys::EVP_PKEY_is_a(pkey, alg.as_ptr()) == 1 }
444 }
445
446 #[must_use]
452 pub fn public_key_bits(&self) -> Option<u32> {
453 let pkey = unsafe { sys::X509_get0_pubkey(self.ptr) };
458 if pkey.is_null() {
459 return None;
460 }
461 u32::try_from(unsafe { sys::EVP_PKEY_get_bits(pkey) }).ok()
463 }
464
465 pub fn signature_info(&self) -> Result<SignatureInfo, ErrorStack> {
481 let mut md_nid: std::ffi::c_int = 0;
482 let mut pk_nid: std::ffi::c_int = 0;
483 let mut security_bits: std::ffi::c_int = 0;
484 let rc = unsafe {
492 sys::X509_get_signature_info(
493 self.ptr,
494 &raw mut md_nid,
495 &raw mut pk_nid,
496 &raw mut security_bits,
497 std::ptr::null_mut(),
498 )
499 };
500 if rc != 1 {
501 return Err(ErrorStack::drain());
502 }
503 Ok(SignatureInfo {
504 md_nid,
505 pk_nid,
506 security_bits,
507 })
508 }
509
510 pub fn verify(&self, key: &crate::pkey::Pkey<crate::pkey::Public>) -> Result<bool, ErrorStack> {
517 match unsafe { sys::X509_verify(self.ptr, key.as_ptr()) } {
518 1 => Ok(true),
519 0 => Ok(false),
520 _ => Err(ErrorStack::drain()),
521 }
522 }
523
524 #[must_use]
526 pub fn is_self_signed(&self) -> bool {
527 unsafe { sys::X509_self_signed(self.ptr, 0) == 1 }
529 }
530
531 #[must_use]
533 pub fn extension_count(&self) -> usize {
534 let n = unsafe { sys::X509_get_ext_count(self.ptr) };
535 usize::try_from(n).unwrap_or(0)
536 }
537
538 #[must_use]
542 pub fn extension(&self, idx: usize) -> Option<X509Extension<'_>> {
543 let idx_i32 = i32::try_from(idx).ok()?;
544 let ptr = unsafe { sys::X509_get_ext(self.ptr, idx_i32) }.cast::<sys::X509_EXTENSION>();
546 if ptr.is_null() {
547 None
548 } else {
549 Some(X509Extension {
550 ptr,
551 _owner: PhantomData,
552 })
553 }
554 }
555
556 #[must_use]
560 pub fn extension_by_nid(&self, nid: i32) -> Option<X509Extension<'_>> {
561 let idx = unsafe { sys::X509_get_ext_by_NID(self.ptr, nid, -1) };
562 if idx < 0 {
563 return None;
564 }
565 let ptr = unsafe { sys::X509_get_ext(self.ptr, idx) }.cast::<sys::X509_EXTENSION>();
566 if ptr.is_null() {
567 None
568 } else {
569 Some(X509Extension {
570 ptr,
571 _owner: PhantomData,
572 })
573 }
574 }
575
576 #[must_use]
585 pub fn extension_der(&self, nid: i32) -> Option<&[u8]> {
586 let idx = unsafe { sys::X509_get_ext_by_NID(self.ptr, nid, -1) };
588 if idx < 0 {
589 return None;
590 }
591 let ext = unsafe { sys::X509_get_ext(self.ptr, idx) };
593 if ext.is_null() {
594 return None;
595 }
596 let data = unsafe { sys::X509_EXTENSION_get_data(ext) };
599 if data.is_null() {
600 return Some(&[]);
601 }
602 Some(unsafe { asn1_string_data(data.cast()) })
605 }
606
607 #[must_use]
614 pub fn key_usage(&self) -> Option<KeyUsage> {
615 let bits = unsafe { sys::X509_get_key_usage(self.ptr) };
619 if bits == u32::MAX {
620 None
621 } else {
622 Some(KeyUsage(bits))
623 }
624 }
625
626 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
637 pub fn extended_key_usage_oids(&self) -> Result<Vec<Oid>, ErrorStack> {
638 let nid = unsafe { sys::OBJ_sn2nid(c"extendedKeyUsage".as_ptr()) };
639 let stack_ptr = unsafe {
642 sys::X509_get_ext_d2i(self.ptr, nid, std::ptr::null_mut(), std::ptr::null_mut())
643 };
644 if stack_ptr.is_null() {
645 return Ok(Vec::new());
646 }
647 let stack = stack_ptr.cast::<sys::OPENSSL_STACK>();
648 let n = usize::try_from(unsafe { sys::OPENSSL_sk_num(stack) }).unwrap_or(0);
649 let mut oids = Vec::with_capacity(n);
650 for i in 0..n {
651 let raw = unsafe { sys::OPENSSL_sk_value(stack, i as i32) };
652 if raw.is_null() {
653 continue;
654 }
655 let dup = unsafe { sys::OBJ_dup(raw.cast::<sys::ASN1_OBJECT>()) };
657 if dup.is_null() {
658 unsafe {
662 sys::OPENSSL_sk_pop_free(stack, Some(free_asn1_object));
663 }
664 return Err(ErrorStack::drain());
665 }
666 oids.push(unsafe { Oid::from_ptr(dup) });
668 }
669 unsafe { sys::OPENSSL_sk_pop_free(stack, Some(free_asn1_object)) };
671 Ok(oids)
672 }
673
674 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
687 pub fn subject_alt_names(&self) -> Result<Vec<GeneralName>, ErrorStack> {
688 let nid = unsafe { sys::OBJ_sn2nid(c"subjectAltName".as_ptr()) };
689 let stack_ptr = unsafe {
691 sys::X509_get_ext_d2i(self.ptr, nid, std::ptr::null_mut(), std::ptr::null_mut())
692 };
693 if stack_ptr.is_null() {
694 return Ok(Vec::new());
695 }
696 let stack = stack_ptr.cast::<sys::OPENSSL_STACK>();
697 let n = usize::try_from(unsafe { sys::OPENSSL_sk_num(stack) }).unwrap_or(0);
698 let mut names = Vec::with_capacity(n);
699 for i in 0..n {
700 let raw = unsafe { sys::OPENSSL_sk_value(stack, i as i32) };
701 if raw.is_null() {
702 continue;
703 }
704 let gn = raw.cast::<sys::GENERAL_NAME>();
705 let gn_ref = unsafe { &*gn };
707 match gn_ref.type_ {
709 1 => {
711 let s = unsafe { asn1_string_data(gn_ref.d.rfc822Name.cast()) };
712 names.push(GeneralName::Email(String::from_utf8_lossy(s).into_owned()));
713 }
714 2 => {
716 let s = unsafe { asn1_string_data(gn_ref.d.dNSName.cast()) };
717 names.push(GeneralName::Dns(String::from_utf8_lossy(s).into_owned()));
718 }
719 6 => {
721 let s = unsafe { asn1_string_data(gn_ref.d.uniformResourceIdentifier.cast()) };
722 names.push(GeneralName::Uri(String::from_utf8_lossy(s).into_owned()));
723 }
724 7 => {
726 let b = unsafe { asn1_string_data(gn_ref.d.iPAddress.cast()) };
727 names.push(GeneralName::IpAddress(b.to_vec()));
728 }
729 8 => {
731 let rid = unsafe { gn_ref.d.registeredID };
732 if !rid.is_null() {
733 let dup = unsafe { sys::OBJ_dup(rid) };
734 if !dup.is_null() {
735 names.push(GeneralName::RegisteredId(unsafe { Oid::from_ptr(dup) }));
736 }
737 }
738 }
739 0 => {
741 let other = unsafe { gn_ref.d.otherName };
742 if !other.is_null() {
743 let other_ref = unsafe { &*other };
744 if !other_ref.type_id.is_null() {
745 let oid_dup = unsafe { sys::OBJ_dup(other_ref.type_id) };
746 if !oid_dup.is_null() {
747 let value = unsafe { i2d_asn1_type_to_vec(other_ref.value) };
748 names.push(GeneralName::OtherName {
749 type_id: unsafe { Oid::from_ptr(oid_dup) },
750 value,
751 });
752 }
753 }
754 }
755 }
756 _ => {}
758 }
759 }
760 unsafe { sys::OPENSSL_sk_pop_free(stack, Some(free_general_name)) };
762 Ok(names)
763 }
764
765 #[must_use]
767 #[allow(dead_code)] pub(crate) fn as_ptr(&self) -> *mut sys::X509 {
769 self.ptr
770 }
771}
772
773pub struct X509Name<'cert> {
777 ptr: *mut sys::X509_NAME,
778 _owner: PhantomData<&'cert X509>,
779}
780
781impl X509Name<'_> {
782 #[must_use]
784 pub fn entry_count(&self) -> usize {
785 usize::try_from(unsafe { sys::X509_NAME_entry_count(self.ptr) }).unwrap_or(0)
786 }
787
788 #[must_use]
790 pub fn entry(&self, idx: usize) -> Option<X509NameEntry<'_>> {
791 let idx_i32 = i32::try_from(idx).ok()?;
792 let ptr =
794 unsafe { sys::X509_NAME_get_entry(self.ptr, idx_i32) }.cast::<sys::X509_NAME_ENTRY>();
795 if ptr.is_null() {
796 None
797 } else {
798 Some(X509NameEntry {
799 ptr,
800 _owner: PhantomData,
801 })
802 }
803 }
804
805 #[must_use]
814 pub fn to_oneline(&self) -> Option<String> {
815 let ptr = unsafe { sys::X509_NAME_oneline(self.ptr, std::ptr::null_mut(), 0) };
820 if ptr.is_null() {
821 return None;
822 }
823 let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
825 .to_string_lossy()
826 .into_owned();
827 unsafe { sys::CRYPTO_free(ptr.cast(), c"x509.rs".as_ptr(), 0) };
831 Some(s)
832 }
833
834 #[must_use]
839 pub fn to_string(&self) -> Option<String> {
840 let mut bio = MemBio::new().ok()?;
841 let n = unsafe { sys::X509_NAME_print_ex(bio.as_ptr(), self.ptr, 0, 0) };
843 if n < 0 {
844 return None;
845 }
846 String::from_utf8(bio.into_vec()).ok()
847 }
848}
849
850pub struct X509NameEntry<'name> {
854 ptr: *mut sys::X509_NAME_ENTRY,
855 _owner: PhantomData<&'name ()>,
856}
857
858impl X509NameEntry<'_> {
859 #[must_use]
861 pub fn nid(&self) -> i32 {
862 let obj = unsafe { sys::X509_NAME_ENTRY_get_object(self.ptr) };
863 unsafe { sys::OBJ_obj2nid(obj) }
864 }
865
866 #[must_use]
870 pub fn data(&self) -> &[u8] {
871 let asn1 = unsafe { sys::X509_NAME_ENTRY_get_data(self.ptr) };
872 if asn1.is_null() {
873 return &[];
874 }
875 unsafe { asn1_string_data(asn1) }
877 }
878}
879
880pub struct X509Extension<'cert> {
884 ptr: *mut sys::X509_EXTENSION,
885 _owner: PhantomData<&'cert X509>,
886}
887
888impl X509Extension<'_> {
889 #[must_use]
891 pub fn nid(&self) -> i32 {
892 let obj = unsafe { sys::X509_EXTENSION_get_object(self.ptr) };
893 unsafe { sys::OBJ_obj2nid(obj) }
894 }
895
896 #[must_use]
898 pub fn is_critical(&self) -> bool {
899 unsafe { sys::X509_EXTENSION_get_critical(self.ptr) == 1 }
900 }
901
902 #[must_use]
906 pub fn data(&self) -> &[u8] {
907 let asn1 = unsafe { sys::X509_EXTENSION_get_data(self.ptr) };
908 if asn1.is_null() {
909 return &[];
910 }
911 unsafe { asn1_string_data(asn1.cast()) }
914 }
915}
916
917pub struct X509NameOwned {
923 ptr: *mut sys::X509_NAME,
924}
925
926impl X509NameOwned {
927 pub fn new() -> Result<Self, ErrorStack> {
931 let ptr = unsafe { sys::X509_NAME_new() };
932 if ptr.is_null() {
933 return Err(ErrorStack::drain());
934 }
935 Ok(X509NameOwned { ptr })
936 }
937
938 pub fn add_entry_by_txt(&mut self, field: &CStr, value: &[u8]) -> Result<(), ErrorStack> {
948 let len = i32::try_from(value.len()).map_err(|_| ErrorStack::drain())?;
949 let rc = unsafe {
951 sys::X509_NAME_add_entry_by_txt(
952 self.ptr,
953 field.as_ptr(),
954 0x1000, value.as_ptr(),
956 len,
957 -1, 0,
959 )
960 };
961 if rc != 1 {
962 return Err(ErrorStack::drain());
963 }
964 Ok(())
965 }
966}
967
968impl Drop for X509NameOwned {
969 fn drop(&mut self) {
970 unsafe { sys::X509_NAME_free(self.ptr) };
971 }
972}
973
974pub struct X509Builder {
994 ptr: *mut sys::X509,
995}
996
997impl X509Builder {
998 pub fn new() -> Result<Self, ErrorStack> {
1002 let ptr = unsafe { sys::X509_new() };
1003 if ptr.is_null() {
1004 return Err(ErrorStack::drain());
1005 }
1006 Ok(X509Builder { ptr })
1007 }
1008
1009 pub fn set_version(self, version: i64) -> Result<Self, ErrorStack> {
1013 crate::ossl_call!(sys::X509_set_version(self.ptr, version))?;
1014 Ok(self)
1015 }
1016
1017 pub fn set_serial_number(self, n: i64) -> Result<Self, ErrorStack> {
1021 let ai = unsafe { sys::ASN1_INTEGER_new() };
1022 if ai.is_null() {
1023 return Err(ErrorStack::drain());
1024 }
1025 crate::ossl_call!(sys::ASN1_INTEGER_set_int64(ai, n)).map_err(|e| {
1026 unsafe { sys::ASN1_INTEGER_free(ai) };
1027 e
1028 })?;
1029 let rc = unsafe { sys::X509_set_serialNumber(self.ptr, ai) };
1030 unsafe { sys::ASN1_INTEGER_free(ai) };
1031 if rc != 1 {
1032 return Err(ErrorStack::drain());
1033 }
1034 Ok(self)
1035 }
1036
1037 pub fn set_not_before_offset(self, offset_secs: i64) -> Result<Self, ErrorStack> {
1041 let t = unsafe { sys::X509_getm_notBefore(self.ptr) };
1042 if unsafe { sys::X509_gmtime_adj(t, offset_secs) }.is_null() {
1043 return Err(ErrorStack::drain());
1044 }
1045 Ok(self)
1046 }
1047
1048 pub fn set_not_after_offset(self, offset_secs: i64) -> Result<Self, ErrorStack> {
1052 let t = unsafe { sys::X509_getm_notAfter(self.ptr) };
1053 if unsafe { sys::X509_gmtime_adj(t, offset_secs) }.is_null() {
1054 return Err(ErrorStack::drain());
1055 }
1056 Ok(self)
1057 }
1058
1059 pub fn set_subject_name(self, name: &X509NameOwned) -> Result<Self, ErrorStack> {
1063 crate::ossl_call!(sys::X509_set_subject_name(self.ptr, name.ptr))?;
1064 Ok(self)
1065 }
1066
1067 pub fn set_issuer_name(self, name: &X509NameOwned) -> Result<Self, ErrorStack> {
1071 crate::ossl_call!(sys::X509_set_issuer_name(self.ptr, name.ptr))?;
1072 Ok(self)
1073 }
1074
1075 pub fn set_public_key<T: crate::pkey::HasPublic>(
1079 self,
1080 key: &crate::pkey::Pkey<T>,
1081 ) -> Result<Self, ErrorStack> {
1082 crate::ossl_call!(sys::X509_set_pubkey(self.ptr, key.as_ptr()))?;
1083 Ok(self)
1084 }
1085
1086 pub fn sign(
1093 self,
1094 key: &crate::pkey::Pkey<crate::pkey::Private>,
1095 digest: Option<&crate::digest::DigestAlg>,
1096 ) -> Result<Self, ErrorStack> {
1097 let md_ptr = digest.map_or(std::ptr::null(), crate::digest::DigestAlg::as_ptr);
1098 let rc = unsafe { sys::X509_sign(self.ptr, key.as_ptr(), md_ptr) };
1100 if rc <= 0 {
1101 return Err(ErrorStack::drain());
1102 }
1103 Ok(self)
1104 }
1105
1106 #[must_use]
1108 pub fn build(self) -> X509 {
1109 let ptr = self.ptr;
1110 std::mem::forget(self);
1111 X509 { ptr }
1112 }
1113}
1114
1115impl Drop for X509Builder {
1116 fn drop(&mut self) {
1117 unsafe { sys::X509_free(self.ptr) };
1118 }
1119}
1120
1121pub struct X509Store {
1127 ptr: *mut sys::X509_STORE,
1128}
1129
1130unsafe impl Send for X509Store {}
1131unsafe impl Sync for X509Store {}
1132
1133impl Clone for X509Store {
1134 fn clone(&self) -> Self {
1135 unsafe { sys::X509_STORE_up_ref(self.ptr) };
1136 X509Store { ptr: self.ptr }
1137 }
1138}
1139
1140impl Drop for X509Store {
1141 fn drop(&mut self) {
1142 unsafe { sys::X509_STORE_free(self.ptr) };
1143 }
1144}
1145
1146impl X509Store {
1147 pub fn new() -> Result<Self, ErrorStack> {
1151 let ptr = unsafe { sys::X509_STORE_new() };
1152 if ptr.is_null() {
1153 return Err(ErrorStack::drain());
1154 }
1155 Ok(X509Store { ptr })
1156 }
1157
1158 pub fn add_cert(&mut self, cert: &X509) -> Result<(), ErrorStack> {
1164 let rc = unsafe { sys::X509_STORE_add_cert(self.ptr, cert.ptr) };
1165 if rc != 1 {
1166 return Err(ErrorStack::drain());
1167 }
1168 Ok(())
1169 }
1170
1171 pub fn add_crl(&mut self, crl: &X509Crl) -> Result<(), ErrorStack> {
1175 let rc = unsafe { sys::X509_STORE_add_crl(self.ptr, crl.ptr) };
1176 if rc != 1 {
1177 return Err(ErrorStack::drain());
1178 }
1179 Ok(())
1180 }
1181
1182 pub fn set_flags(&mut self, flags: u64) -> Result<(), ErrorStack> {
1186 let rc = unsafe { sys::X509_STORE_set_flags(self.ptr, flags) };
1187 if rc != 1 {
1188 return Err(ErrorStack::drain());
1189 }
1190 Ok(())
1191 }
1192
1193 #[must_use]
1195 pub(crate) fn as_ptr(&self) -> *mut sys::X509_STORE {
1196 self.ptr
1197 }
1198}
1199
1200pub struct X509StoreCtx {
1207 ptr: *mut sys::X509_STORE_CTX,
1208}
1209
1210impl Drop for X509StoreCtx {
1211 fn drop(&mut self) {
1212 unsafe { sys::X509_STORE_CTX_free(self.ptr) };
1213 }
1214}
1215
1216unsafe impl Send for X509StoreCtx {}
1217
1218impl X509StoreCtx {
1219 pub fn new() -> Result<Self, ErrorStack> {
1223 let ptr = unsafe { sys::X509_STORE_CTX_new() };
1224 if ptr.is_null() {
1225 return Err(ErrorStack::drain());
1226 }
1227 Ok(X509StoreCtx { ptr })
1228 }
1229
1230 pub fn init(&mut self, store: &X509Store, cert: &X509) -> Result<(), ErrorStack> {
1236 let rc = unsafe {
1237 sys::X509_STORE_CTX_init(self.ptr, store.ptr, cert.ptr, std::ptr::null_mut())
1238 };
1239 if rc != 1 {
1240 return Err(ErrorStack::drain());
1241 }
1242 Ok(())
1243 }
1244
1245 pub fn verify(&mut self) -> Result<bool, ErrorStack> {
1252 match unsafe { sys::X509_verify_cert(self.ptr) } {
1253 1 => Ok(true),
1254 0 => Ok(false),
1255 _ => Err(ErrorStack::drain()),
1256 }
1257 }
1258
1259 #[must_use]
1264 pub fn error(&self) -> i32 {
1265 unsafe { sys::X509_STORE_CTX_get_error(self.ptr) }
1266 }
1267
1268 #[must_use]
1273 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
1275 pub fn chain(&self) -> Vec<X509> {
1276 let stack = unsafe { sys::X509_STORE_CTX_get0_chain(self.ptr) };
1277 if stack.is_null() {
1278 return Vec::new();
1279 }
1280 let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
1281 let n = usize::try_from(n).unwrap_or(0);
1282 let mut out = Vec::with_capacity(n);
1283 for i in 0..n {
1284 let raw =
1285 unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i as i32) };
1286 if raw.is_null() {
1287 continue;
1288 }
1289 let cert_ptr = raw.cast::<sys::X509>();
1291 unsafe { sys::X509_up_ref(cert_ptr) };
1292 out.push(X509 { ptr: cert_ptr });
1293 }
1294 out
1295 }
1296}
1297
1298pub struct X509Crl {
1304 ptr: *mut sys::X509_CRL,
1305}
1306
1307unsafe impl Send for X509Crl {}
1308unsafe impl Sync for X509Crl {}
1309
1310impl Clone for X509Crl {
1311 fn clone(&self) -> Self {
1312 unsafe { sys::X509_CRL_up_ref(self.ptr) };
1313 X509Crl { ptr: self.ptr }
1314 }
1315}
1316
1317impl Drop for X509Crl {
1318 fn drop(&mut self) {
1319 unsafe { sys::X509_CRL_free(self.ptr) };
1320 }
1321}
1322
1323impl X509Crl {
1324 pub(crate) unsafe fn from_ptr(ptr: *mut sys::X509_CRL) -> Self {
1330 X509Crl { ptr }
1331 }
1332
1333 pub fn new() -> Result<Self, ErrorStack> {
1342 let ptr = unsafe { sys::X509_CRL_new() };
1347 if ptr.is_null() {
1348 return Err(ErrorStack::drain());
1349 }
1350 Ok(unsafe { Self::from_ptr(ptr) })
1351 }
1352
1353 pub fn new_in(ctx: &std::sync::Arc<crate::lib_ctx::LibCtx>) -> Result<Self, ErrorStack> {
1362 let ptr = unsafe { sys::X509_CRL_new_ex(ctx.as_ptr(), std::ptr::null()) };
1369 if ptr.is_null() {
1370 return Err(ErrorStack::drain());
1371 }
1372 Ok(unsafe { Self::from_ptr(ptr) })
1373 }
1374
1375 pub fn from_pem(pem: &[u8]) -> Result<Self, ErrorStack> {
1379 let bio = MemBioBuf::new(pem)?;
1380 let ptr = unsafe {
1381 sys::PEM_read_bio_X509_CRL(
1382 bio.as_ptr(),
1383 std::ptr::null_mut(),
1384 None,
1385 std::ptr::null_mut(),
1386 )
1387 };
1388 if ptr.is_null() {
1389 return Err(ErrorStack::drain());
1390 }
1391 Ok(unsafe { Self::from_ptr(ptr) })
1392 }
1393
1394 pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack> {
1398 let bio = MemBioBuf::new(der)?;
1399 let ptr = unsafe { sys::d2i_X509_CRL_bio(bio.as_ptr(), std::ptr::null_mut()) };
1400 if ptr.is_null() {
1401 return Err(ErrorStack::drain());
1402 }
1403 Ok(unsafe { Self::from_ptr(ptr) })
1404 }
1405
1406 pub fn to_pem(&self) -> Result<Vec<u8>, ErrorStack> {
1410 let mut bio = MemBio::new()?;
1411 let rc = unsafe { sys::PEM_write_bio_X509_CRL(bio.as_ptr(), self.ptr) };
1412 if rc != 1 {
1413 return Err(ErrorStack::drain());
1414 }
1415 Ok(bio.into_vec())
1416 }
1417
1418 pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack> {
1422 let mut bio = MemBio::new()?;
1423 let rc = unsafe { sys::i2d_X509_CRL_bio(bio.as_ptr(), self.ptr) };
1424 if rc != 1 {
1425 return Err(ErrorStack::drain());
1426 }
1427 Ok(bio.into_vec())
1428 }
1429
1430 #[must_use]
1432 pub fn issuer_name(&self) -> X509Name<'_> {
1433 let ptr = unsafe { sys::X509_CRL_get_issuer(self.ptr) };
1434 X509Name {
1435 ptr: ptr.cast(),
1436 _owner: PhantomData,
1437 }
1438 }
1439
1440 #[must_use]
1442 pub fn last_update_str(&self) -> Option<String> {
1443 let t = unsafe { sys::X509_CRL_get0_lastUpdate(self.ptr) };
1444 asn1_time_to_str(t)
1445 }
1446
1447 #[must_use]
1449 pub fn next_update_str(&self) -> Option<String> {
1450 let t = unsafe { sys::X509_CRL_get0_nextUpdate(self.ptr) };
1451 asn1_time_to_str(t)
1452 }
1453
1454 pub fn verify(&self, key: &crate::pkey::Pkey<crate::pkey::Public>) -> Result<bool, ErrorStack> {
1460 match unsafe { sys::X509_CRL_verify(self.ptr, key.as_ptr()) } {
1461 1 => Ok(true),
1462 0 => Ok(false),
1463 _ => Err(ErrorStack::drain()),
1464 }
1465 }
1466
1467 #[must_use]
1469 #[allow(dead_code)]
1470 pub(crate) fn as_ptr(&self) -> *mut sys::X509_CRL {
1471 self.ptr
1472 }
1473}
1474
1475#[must_use]
1481pub fn nid_from_short_name(sn: &CStr) -> Option<i32> {
1482 let nid = unsafe { sys::OBJ_sn2nid(sn.as_ptr()) };
1488 if nid == 0 {
1489 None
1490 } else {
1491 Some(nid)
1492 }
1493}
1494
1495#[must_use]
1502pub fn nid_from_text(s: &CStr) -> Option<i32> {
1503 let nid = unsafe { sys::OBJ_txt2nid(s.as_ptr()) };
1509 if nid == 0 {
1510 None
1511 } else {
1512 Some(nid)
1513 }
1514}
1515
1516#[must_use]
1526pub fn nid_to_short_name(nid: i32) -> Option<&'static CStr> {
1527 let ptr = unsafe { sys::OBJ_nid2sn(nid) };
1533 if ptr.is_null() {
1534 return None;
1535 }
1536 Some(unsafe { CStr::from_ptr(ptr) })
1539}
1540
1541#[must_use]
1548pub fn nid_to_long_name(nid: i32) -> Option<&'static CStr> {
1549 let ptr = unsafe { sys::OBJ_nid2ln(nid) };
1552 if ptr.is_null() {
1553 return None;
1554 }
1555 Some(unsafe { CStr::from_ptr(ptr) })
1558}
1559
1560fn asn1_time_to_broken_down(t: *const sys::ASN1_TIME) -> Option<BrokenDownTime> {
1564 if t.is_null() {
1565 return None;
1566 }
1567 let mut tm = unsafe { std::mem::zeroed::<sys::tm>() };
1572 let rc = unsafe { sys::ASN1_TIME_to_tm(t, &raw mut tm) };
1573 if rc != 1 {
1574 return None;
1575 }
1576 Some(BrokenDownTime {
1577 year: tm.tm_year + 1900,
1578 month: u8::try_from(tm.tm_mon + 1).unwrap_or(0),
1579 day: u8::try_from(tm.tm_mday).unwrap_or(0),
1580 hour: u8::try_from(tm.tm_hour).unwrap_or(0),
1581 minute: u8::try_from(tm.tm_min).unwrap_or(0),
1582 second: u8::try_from(tm.tm_sec).unwrap_or(0),
1583 })
1584}
1585
1586fn asn1_time_to_str(t: *const sys::ASN1_TIME) -> Option<String> {
1588 if t.is_null() {
1589 return None;
1590 }
1591 let mut bio = MemBio::new().ok()?;
1592 let rc = unsafe { sys::ASN1_TIME_print(bio.as_ptr(), t) };
1593 if rc != 1 {
1594 return None;
1595 }
1596 String::from_utf8(bio.into_vec()).ok()
1597}
1598
1599unsafe extern "C" fn free_asn1_object(p: *mut std::os::raw::c_void) {
1609 unsafe { sys::ASN1_OBJECT_free(p.cast()) }
1610}
1611
1612unsafe extern "C" fn free_general_name(p: *mut std::os::raw::c_void) {
1614 unsafe { sys::GENERAL_NAME_free(p.cast()) }
1615}
1616
1617unsafe fn i2d_asn1_type_to_vec(typ: *mut sys::ASN1_TYPE) -> Vec<u8> {
1620 if typ.is_null() {
1621 return Vec::new();
1622 }
1623 let len = unsafe { sys::i2d_ASN1_TYPE(typ, std::ptr::null_mut()) };
1624 let Ok(len_usize) = usize::try_from(len) else {
1625 return Vec::new();
1626 };
1627 let mut buf = vec![0u8; len_usize];
1628 let mut ptr = buf.as_mut_ptr();
1629 let written = unsafe { sys::i2d_ASN1_TYPE(typ, std::ptr::addr_of_mut!(ptr)) };
1630 let Ok(written_usize) = usize::try_from(written) else {
1631 return Vec::new();
1632 };
1633 buf.truncate(written_usize);
1634 buf
1635}
1636
1637unsafe fn asn1_string_data<'a>(asn1: *const sys::ASN1_STRING) -> &'a [u8] {
1638 let len = usize::try_from(sys::ASN1_STRING_length(asn1)).unwrap_or(0);
1639 let ptr = sys::ASN1_STRING_get0_data(asn1);
1640 if ptr.is_null() || len == 0 {
1641 return &[];
1642 }
1643 std::slice::from_raw_parts(ptr, len)
1645}
1646
1647#[cfg(test)]
1650mod tests {
1651 use super::*;
1652 use crate::pkey::{KeygenCtx, Pkey, Private, Public};
1653
1654 fn make_self_signed() -> (X509, Pkey<Private>, Pkey<Public>) {
1656 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1657 let priv_key = kgen.generate().unwrap();
1658 let pub_key = Pkey::<Public>::from(priv_key.clone());
1659
1660 let mut name = X509NameOwned::new().unwrap();
1661 name.add_entry_by_txt(c"CN", b"Test Cert").unwrap();
1662 name.add_entry_by_txt(c"O", b"Example Org").unwrap();
1663
1664 let cert = X509Builder::new()
1665 .unwrap()
1666 .set_version(2)
1667 .unwrap()
1668 .set_serial_number(1)
1669 .unwrap()
1670 .set_not_before_offset(0)
1671 .unwrap()
1672 .set_not_after_offset(365 * 86400)
1673 .unwrap()
1674 .set_subject_name(&name)
1675 .unwrap()
1676 .set_issuer_name(&name)
1677 .unwrap()
1678 .set_public_key(&pub_key)
1679 .unwrap()
1680 .sign(&priv_key, None)
1681 .unwrap()
1682 .build();
1683
1684 (cert, priv_key, pub_key)
1685 }
1686
1687 #[test]
1688 fn build_and_verify_self_signed() {
1689 let (cert, _, pub_key) = make_self_signed();
1690 assert!(cert.verify(&pub_key).unwrap());
1691 assert!(cert.is_self_signed());
1692 }
1693
1694 #[test]
1695 fn pem_round_trip() {
1696 let (cert, _, _) = make_self_signed();
1697 let pem = cert.to_pem().unwrap();
1698 assert!(pem.starts_with(b"-----BEGIN CERTIFICATE-----"));
1699
1700 let cert2 = X509::from_pem(&pem).unwrap();
1701 assert_eq!(cert.to_der().unwrap(), cert2.to_der().unwrap());
1703 }
1704
1705 #[test]
1706 fn der_round_trip() {
1707 let (cert, _, _) = make_self_signed();
1708 let der = cert.to_der().unwrap();
1709 assert!(!der.is_empty());
1710
1711 let cert2 = X509::from_der(&der).unwrap();
1712 assert_eq!(cert2.to_der().unwrap(), der);
1713 }
1714
1715 #[test]
1716 fn subject_name_entries() {
1717 let (cert, _, _) = make_self_signed();
1718 let name = cert.subject_name();
1719
1720 assert_eq!(name.entry_count(), 2);
1721
1722 let e0 = name.entry(0).unwrap();
1724 assert_eq!(e0.nid(), 13); assert!(!e0.data().is_empty());
1726
1727 let s = name.to_string().unwrap();
1729 assert!(s.contains("Test Cert") || s.contains("CN=Test Cert"));
1730 }
1731
1732 #[test]
1733 fn serial_number() {
1734 let (cert, _, _) = make_self_signed();
1735 assert_eq!(cert.serial_number(), Some(1));
1736 }
1737
1738 #[test]
1739 fn validity_strings_present() {
1740 let (cert, _, _) = make_self_signed();
1741 let nb = cert.not_before_str().unwrap();
1742 let na = cert.not_after_str().unwrap();
1743 assert!(nb.contains("GMT"), "not_before_str = {nb:?}");
1745 assert!(na.contains("GMT"), "not_after_str = {na:?}");
1746 }
1747
1748 #[test]
1749 fn is_valid_now() {
1750 let (cert, _, _) = make_self_signed();
1751 assert!(cert.is_valid_now());
1752 }
1753
1754 #[test]
1755 fn public_key_extraction() {
1756 let (cert, _, pub_key) = make_self_signed();
1757 let extracted = cert.public_key().unwrap();
1758 assert!(extracted.is_a(c"ED25519"));
1760 assert_eq!(pub_key.bits(), extracted.bits());
1761 }
1762
1763 #[test]
1764 fn clone_cert() {
1765 let (cert, _, pub_key) = make_self_signed();
1766 let cert2 = cert.clone();
1767 assert_eq!(cert.to_der().unwrap(), cert2.to_der().unwrap());
1769 assert!(cert2.verify(&pub_key).unwrap());
1770 }
1771
1772 #[test]
1773 fn verify_fails_with_wrong_key() {
1774 let (cert, _, _) = make_self_signed();
1775 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
1777 let other_priv = kgen.generate().unwrap();
1778 let other_pub = Pkey::<Public>::from(other_priv);
1779
1780 assert!(!cert.verify(&other_pub).unwrap());
1782 }
1783
1784 #[test]
1787 fn x509_store_add_cert_and_verify() {
1788 let (cert, _, _) = make_self_signed();
1789
1790 let mut store = X509Store::new().unwrap();
1791 store.add_cert(&cert).unwrap();
1792
1793 let mut ctx = X509StoreCtx::new().unwrap();
1794 ctx.init(&store, &cert).unwrap();
1795 assert!(ctx.verify().unwrap());
1797 }
1798
1799 #[test]
1800 fn x509_store_verify_untrusted_fails() {
1801 let (cert, _, _) = make_self_signed();
1802 let store = X509Store::new().unwrap();
1804
1805 let mut ctx = X509StoreCtx::new().unwrap();
1806 ctx.init(&store, &cert).unwrap();
1807 assert!(!ctx.verify().unwrap());
1808 assert_ne!(ctx.error(), 0);
1810 }
1811
1812 #[test]
1813 fn x509_store_ctx_chain_populated_after_verify() {
1814 let (cert, _, _) = make_self_signed();
1815 let mut store = X509Store::new().unwrap();
1816 store.add_cert(&cert).unwrap();
1817
1818 let mut ctx = X509StoreCtx::new().unwrap();
1819 ctx.init(&store, &cert).unwrap();
1820 assert!(ctx.verify().unwrap());
1821
1822 let chain = ctx.chain();
1823 assert!(
1824 !chain.is_empty(),
1825 "verified chain should contain at least the leaf"
1826 );
1827 }
1828
1829 #[test]
1832 fn x509crl_new_roundtrip() {
1833 let crl = X509Crl::new().expect("X509_CRL_new should succeed");
1837 let _result = crl.to_der();
1840 drop(crl);
1841 }
1842
1843 #[test]
1844 fn x509crl_new_in_roundtrip() {
1845 use std::sync::Arc;
1846 let ctx = Arc::new(crate::lib_ctx::LibCtx::new().expect("LibCtx::new should succeed"));
1847 let crl = X509Crl::new_in(&ctx).expect("X509_CRL_new_ex should succeed");
1848 let _result = crl.to_der();
1849 drop(crl);
1850 }
1851
1852 const TEST_CRL_PEM: &[u8] = b"\
1854-----BEGIN X509 CRL-----\n\
1855MIIBVjBAMA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBlJTQSBDQRcNMjYwNDE1\n\
1856MTUwNDEzWhcNMjYwNTE1MTUwNDEzWjANBgkqhkiG9w0BAQsFAAOCAQEAi209u0hh\n\
1857Vz42YaqLplQwBoYCjtjETenl4xXRNcFOYU6Y+FmR66XNGkl9HbPClrz3hRMnbBYr\n\
1858OQJfWQOKS9lS0zpEI4qtlH/H1JBNGwiY32HMqf5HULn0w0ARvmoXR4NzsCecK22G\n\
1859gN61k5FCCpPY8HztsuoHMHAQ65W1WfBiTWu8ZH0nCCU0CA4MSaPZUiNt8/mJZzTG\n\
1860UwTGcZ/hcHQMpocBX40nE7ta5opcIpjG+q2uiCWhXwoqmYsLvdJ+Obw20bLirMHt\n\
1861UsmESTw5G+vcRCudoiSw89Z/jzsYq8yuFhRzF9kA/RtqCoQ+ylQSSH5hxzW2+bPd\n\
1862QPHivSGDiUhH6Q==\n\
1863-----END X509 CRL-----\n";
1864
1865 #[test]
1866 fn crl_pem_round_trip() {
1867 let crl = X509Crl::from_pem(TEST_CRL_PEM).unwrap();
1868 let issuer = crl.issuer_name();
1870 assert!(issuer.entry_count() > 0);
1871 assert!(crl.last_update_str().is_some());
1873 assert!(crl.next_update_str().is_some());
1874 let pem = crl.to_pem().unwrap();
1876 assert!(pem.starts_with(b"-----BEGIN X509 CRL-----"));
1877 }
1878
1879 #[test]
1880 fn crl_der_round_trip() {
1881 let crl = X509Crl::from_pem(TEST_CRL_PEM).unwrap();
1882 let der = crl.to_der().unwrap();
1883 assert!(!der.is_empty());
1884 let crl2 = X509Crl::from_der(&der).unwrap();
1885 assert_eq!(crl2.to_der().unwrap(), der);
1886 }
1887
1888 #[test]
1889 fn crl_clone() {
1890 let crl = X509Crl::from_pem(TEST_CRL_PEM).unwrap();
1891 let crl2 = crl.clone();
1892 assert_eq!(crl.to_der().unwrap(), crl2.to_der().unwrap());
1893 }
1894
1895 #[test]
1898 fn x509_nid_from_short_name_known() {
1899 let nid = nid_from_short_name(c"SHA256");
1902 assert!(nid.is_some(), "SHA256 should be a known short name");
1903 assert_eq!(nid_from_short_name(c"CN"), Some(13));
1904 }
1905
1906 #[test]
1907 fn x509_nid_from_short_name_unknown() {
1908 assert_eq!(nid_from_short_name(c"not-a-real-algorithm-xyz"), None);
1909 }
1910
1911 #[test]
1912 fn x509_nid_from_text_dotted_oid() {
1913 assert_eq!(nid_from_text(c"2.5.4.3"), Some(13));
1915 }
1916
1917 #[test]
1918 fn x509_nid_from_text_short_name() {
1919 let via_sn = nid_from_short_name(c"SHA256");
1922 let via_txt = nid_from_text(c"SHA256");
1923 assert_eq!(
1924 via_sn, via_txt,
1925 "short-name lookup must agree between OBJ_sn2nid and OBJ_txt2nid"
1926 );
1927 }
1928
1929 #[test]
1930 fn x509_nid_from_text_unknown() {
1931 assert_eq!(nid_from_text(c"9.9.9.9.9.9.9.9"), None);
1932 }
1933
1934 #[test]
1937 fn x509_serial_number_bytes_small_serial() {
1938 let (cert, _, _) = make_self_signed();
1939 let bytes = cert.serial_number_bytes().unwrap();
1941 assert!(!bytes.is_empty());
1942 assert_eq!(*bytes.last().unwrap(), 1u8);
1943 }
1944
1945 #[test]
1946 fn x509_serial_number_bytes_consistent_with_serial_number() {
1947 let (cert, _, _) = make_self_signed();
1948 let bytes = cert.serial_number_bytes().unwrap();
1949 let n = cert.serial_number().unwrap();
1950 let be = n.to_be_bytes();
1952 let start = be.iter().position(|&b| b != 0).unwrap_or(7);
1954 assert_eq!(bytes, &be[start..]);
1955 }
1956
1957 #[test]
1960 fn x509_not_before_tm_is_some() {
1961 let (cert, _, _) = make_self_signed();
1962 let tm = cert.not_before_tm().expect("notBefore must be parseable");
1963 assert!(tm.year >= 2026, "year must be 2026 or later");
1965 assert!((1..=12).contains(&tm.month));
1966 assert!((1..=31).contains(&tm.day));
1967 }
1968
1969 #[test]
1970 fn x509_not_after_tm_one_year_after_not_before() {
1971 let (cert, _, _) = make_self_signed();
1972 let nb = cert.not_before_tm().unwrap();
1973 let na = cert.not_after_tm().unwrap();
1974 let year_diff = na.year - nb.year;
1977 assert!(year_diff == 0 || year_diff == 1, "year diff must be 0 or 1");
1978 }
1979
1980 #[test]
1981 fn x509_not_before_tm_consistent_with_not_before_str() {
1982 let (cert, _, _) = make_self_signed();
1983 assert!(cert.not_before_tm().is_some());
1985 assert!(cert.not_before_str().is_some());
1986 }
1987
1988 #[test]
1991 fn x509_public_key_is_a_ed25519() {
1992 let (cert, _, _) = make_self_signed();
1993 assert!(cert.public_key_is_a(c"ED25519"));
1994 assert!(!cert.public_key_is_a(c"RSA"));
1995 }
1996
1997 #[test]
1998 fn x509_public_key_bits_ed25519() {
1999 let (cert, _, _) = make_self_signed();
2000 let bits = cert.public_key_bits().unwrap();
2003 assert!(bits > 0, "Ed25519 key must report non-zero bit size");
2004 }
2005
2006 #[test]
2007 fn x509_public_key_bits_agrees_with_public_key_method() {
2008 let (cert, _, _) = make_self_signed();
2009 let owned_bits = cert.public_key().unwrap().bits();
2010 let borrow_bits = cert.public_key_bits().unwrap();
2011 assert_eq!(owned_bits, borrow_bits);
2012 }
2013
2014 #[test]
2017 fn nid_to_short_name_known_nid() {
2018 let sn = nid_to_short_name(13).expect("NID 13 must be known");
2020 assert_eq!(sn.to_bytes(), b"CN");
2021 }
2022
2023 #[test]
2024 fn nid_to_long_name_known_nid() {
2025 let ln = nid_to_long_name(13).expect("NID 13 must be known");
2026 assert_eq!(ln.to_bytes(), b"commonName");
2027 }
2028
2029 #[test]
2030 fn nid_to_short_name_unknown_nid() {
2031 assert!(nid_to_short_name(i32::MAX).is_none());
2033 }
2034
2035 #[test]
2036 fn nid_to_long_name_unknown_nid() {
2037 assert!(nid_to_long_name(i32::MAX).is_none());
2038 }
2039
2040 #[test]
2041 fn nid_to_short_name_sha256() {
2042 let sn = nid_to_short_name(672).expect("NID 672 (SHA256) must be known");
2044 assert_eq!(sn.to_bytes(), b"SHA256");
2045 }
2046
2047 #[test]
2050 fn x509_name_oneline_returns_string() {
2051 let (cert, _, _) = make_self_signed();
2052 let name = cert.subject_name();
2053 let s = name
2054 .to_oneline()
2055 .expect("to_oneline must return Some for a non-empty name");
2056 assert!(
2058 s.contains("CN="),
2059 "to_oneline output should contain CN=: {s:?}"
2060 );
2061 }
2062
2063 #[test]
2066 fn x509_new_in_lib_ctx() {
2067 use crate::lib_ctx::LibCtx;
2068 let ctx = Arc::new(LibCtx::new().expect("LibCtx::new must succeed"));
2069 let cert = X509::new_in(&ctx);
2070 assert!(
2071 cert.is_ok(),
2072 "X509::new_in must succeed with a valid LibCtx"
2073 );
2074 }
2075
2076 #[test]
2079 fn x509_extension_der_absent_nid_returns_none() {
2080 let (cert, _, _) = make_self_signed();
2081 let result = cert.extension_der(85);
2083 assert!(
2084 result.is_none(),
2085 "extension_der must return None for absent extension"
2086 );
2087 }
2088
2089 #[test]
2090 fn x509_extension_der_present_returns_some() {
2091 let (cert, _, _) = make_self_signed();
2094 let count = cert.extension_count();
2097 if count > 0 {
2098 let ext = cert
2099 .extension(0)
2100 .expect("extension(0) must be Some when count > 0");
2101 let nid = ext.nid();
2102 let der = cert
2103 .extension_der(nid)
2104 .expect("extension_der must return Some for a NID that exists in the cert");
2105 assert_eq!(
2107 der,
2108 ext.data(),
2109 "extension_der bytes must match X509Extension::data"
2110 );
2111 }
2112 }
2114}
2115
2116#[cfg(test)]
2117mod signature_info_tests {
2118 use super::*;
2119 use crate::pkey::{KeygenCtx, Pkey, Private, Public};
2120
2121 fn make_ed25519_cert() -> (X509, Pkey<Private>, Pkey<Public>) {
2122 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2123 let priv_key = kgen.generate().unwrap();
2124 let pub_key = Pkey::<Public>::from(priv_key.clone());
2125
2126 let mut name = X509NameOwned::new().unwrap();
2127 name.add_entry_by_txt(c"CN", b"Ed25519 Sig Info Test")
2128 .unwrap();
2129
2130 let cert = X509Builder::new()
2131 .unwrap()
2132 .set_version(2)
2133 .unwrap()
2134 .set_serial_number(42)
2135 .unwrap()
2136 .set_not_before_offset(0)
2137 .unwrap()
2138 .set_not_after_offset(365 * 86400)
2139 .unwrap()
2140 .set_subject_name(&name)
2141 .unwrap()
2142 .set_issuer_name(&name)
2143 .unwrap()
2144 .set_public_key(&pub_key)
2145 .unwrap()
2146 .sign(&priv_key, None)
2147 .unwrap()
2148 .build();
2149
2150 (cert, priv_key, pub_key)
2151 }
2152
2153 #[test]
2157 fn x509_signature_info_ed25519() {
2158 let (cert, _, _) = make_ed25519_cert();
2159 let info = cert
2160 .signature_info()
2161 .expect("signature_info must succeed for a signed cert");
2162
2163 assert_eq!(
2165 info.md_nid, 0,
2166 "Ed25519 md_nid must be 0 (NID_undef); got {}",
2167 info.md_nid
2168 );
2169
2170 assert_ne!(info.pk_nid, 0, "Ed25519 pk_nid must not be NID_undef");
2172 let sn = nid_to_short_name(info.pk_nid)
2173 .expect("Ed25519 pk_nid must have a short name in OpenSSL's OBJ table");
2174 assert_eq!(
2175 sn.to_bytes(),
2176 b"ED25519",
2177 "pk_nid short name must be ED25519; got {sn:?}"
2178 );
2179
2180 assert_eq!(
2182 info.security_bits, 128,
2183 "Ed25519 security bits must be 128; got {}",
2184 info.security_bits
2185 );
2186 }
2187}
2188
2189#[cfg(test)]
2192mod x509_extensions_tests {
2193 use super::*;
2194 use crate::obj::Oid;
2195 use crate::pkey::{KeygenCtx, Pkey, Public};
2196
2197 fn make_ku_eku_san_cert() -> X509 {
2199 let pem = b"-----BEGIN CERTIFICATE-----\n\
2206MIIB3zCCAYWgAwIBAgITO3vrfoe0FOHYovyQw92y4L6ttTAKBggqhkjOPQQDAjAP\n\
2207MQ0wCwYDVQQDDAR0ZXN0MB4XDTI2MDQyNTE1MjUzMFoXDTM2MDQyMjE1MjUzMFow\n\
2208DzENMAsGA1UEAwwEdGVzdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDE//RDS\n\
22095hyz9K7M62qDPsbwg/drLem4n5W0r8e6D6QjQ6WNBca1BABVxXmCKDvSIpKSRa+p\n\
2210Zhv5FbwiO/L2v+Ojgb8wgbwwHQYDVR0OBBYEFJxnt8JgIqZwtrg63T0lgeJBElCk\n\
2211MB8GA1UdIwQYMBaAFJxnt8JgIqZwtrg63T0lgeJBElCkMA8GA1UdEwEB/wQFMAMB\n\
2212Af8wCwYDVR0PBAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA9\n\
2213BgNVHREENjA0ggtleGFtcGxlLmNvbYINKi5leGFtcGxlLm9yZ4cEfwAAAYEQdXNl\n\
2214ckBleGFtcGxlLmNvbTAKBggqhkjOPQQDAgNIADBFAiANTbn0J+j4dpkFxb0JKRoF\n\
22152Dh22JaVPgM+d/t+tt+GfQIhAJlQHlMSntnwVUsE0w1H/aMLoo94HStc1dGGi8/a\n\
22168Hbj\n\
2217-----END CERTIFICATE-----\n";
2218 X509::from_pem(pem).expect("test cert must parse")
2219 }
2220
2221 #[test]
2222 fn key_usage_present_and_correct() {
2223 let cert = make_ku_eku_san_cert();
2224 let ku = cert.key_usage().expect("keyUsage must be present");
2225 assert!(
2226 ku.contains(KeyUsage::DIGITAL_SIGNATURE),
2227 "digitalSignature must be set"
2228 );
2229 assert!(
2230 ku.contains(KeyUsage::KEY_ENCIPHERMENT),
2231 "keyEncipherment must be set"
2232 );
2233 assert!(!ku.contains(KeyUsage::CRL_SIGN), "cRLSign must not be set");
2234 assert!(
2235 !ku.contains(KeyUsage::KEY_CERT_SIGN),
2236 "keyCertSign must not be set"
2237 );
2238 }
2239
2240 fn make_plain_cert() -> X509 {
2241 let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
2242 let priv_key = kgen.generate().unwrap();
2243 let pub_key = Pkey::<Public>::from(priv_key.clone());
2244 let mut name = X509NameOwned::new().unwrap();
2245 name.add_entry_by_txt(c"CN", b"Plain").unwrap();
2246 (|| -> Result<X509, ErrorStack> {
2247 Ok(X509Builder::new()?
2248 .set_version(2)?
2249 .set_serial_number(1)?
2250 .set_not_before_offset(0)?
2251 .set_not_after_offset(365 * 86400)?
2252 .set_subject_name(&name)?
2253 .set_issuer_name(&name)?
2254 .set_public_key(&pub_key)?
2255 .sign(&priv_key, None)?
2256 .build())
2257 })()
2258 .unwrap()
2259 }
2260
2261 #[test]
2262 fn key_usage_absent_returns_none() {
2263 let cert = make_plain_cert();
2264 assert!(cert.key_usage().is_none(), "plain cert has no keyUsage");
2265 }
2266
2267 #[test]
2268 fn extended_key_usage_oids_present() {
2269 let cert = make_ku_eku_san_cert();
2270 let oids = cert.extended_key_usage_oids().unwrap();
2271 assert_eq!(
2272 oids.len(),
2273 2,
2274 "expected 2 EKU OIDs: serverAuth + clientAuth"
2275 );
2276
2277 let server_auth = Oid::from_text("1.3.6.1.5.5.7.3.1").unwrap();
2278 let client_auth = Oid::from_text("1.3.6.1.5.5.7.3.2").unwrap();
2279 assert!(
2280 oids.contains(&server_auth),
2281 "serverAuth OID must be present"
2282 );
2283 assert!(
2284 oids.contains(&client_auth),
2285 "clientAuth OID must be present"
2286 );
2287 }
2288
2289 #[test]
2290 fn extended_key_usage_absent_returns_empty() {
2291 let cert = make_plain_cert();
2292 let oids = cert.extended_key_usage_oids().unwrap();
2293 assert!(oids.is_empty(), "plain cert has no EKU extension");
2294 }
2295
2296 #[test]
2297 fn subject_alt_names_dns_ip_email() {
2298 let cert = make_ku_eku_san_cert();
2299 let sans = cert.subject_alt_names().unwrap();
2300
2301 let dns_names: Vec<_> = sans
2302 .iter()
2303 .filter_map(|n| {
2304 if let GeneralName::Dns(s) = n {
2305 Some(s.as_str())
2306 } else {
2307 None
2308 }
2309 })
2310 .collect();
2311 assert!(
2312 dns_names.contains(&"example.com"),
2313 "example.com DNS SAN must be present"
2314 );
2315 assert!(
2316 dns_names.contains(&"*.example.org"),
2317 "*.example.org DNS SAN must be present"
2318 );
2319
2320 let ips: Vec<_> = sans
2321 .iter()
2322 .filter_map(|n| {
2323 if let GeneralName::IpAddress(b) = n {
2324 Some(b.clone())
2325 } else {
2326 None
2327 }
2328 })
2329 .collect();
2330 assert!(
2331 ips.contains(&vec![127, 0, 0, 1]),
2332 "127.0.0.1 IP SAN must be present"
2333 );
2334
2335 let emails: Vec<_> = sans
2336 .iter()
2337 .filter_map(|n| {
2338 if let GeneralName::Email(s) = n {
2339 Some(s.as_str())
2340 } else {
2341 None
2342 }
2343 })
2344 .collect();
2345 assert!(
2346 emails.contains(&"user@example.com"),
2347 "email SAN must be present"
2348 );
2349 }
2350
2351 #[test]
2352 fn subject_alt_names_absent_returns_empty() {
2353 let cert = make_plain_cert();
2354 let sans = cert.subject_alt_names().unwrap();
2355 assert!(sans.is_empty(), "plain cert has no subjectAltName");
2356 }
2357}