1#![deny(missing_docs)]
2
3use crate::ffi;
28use foreign_types::{ForeignType, ForeignTypeRef};
29use libc::{c_int, c_long, time_t};
30use std::cmp::Ordering;
31use std::ffi::CString;
32use std::fmt;
33use std::ptr;
34use std::slice;
35use std::str;
36
37use crate::bio::MemBio;
38use crate::bn::{BigNum, BigNumRef};
39use crate::error::ErrorStack;
40use crate::nid::Nid;
41use crate::stack::Stackable;
42use crate::string::OpensslString;
43use crate::{cvt, cvt_p};
44use openssl_macros::corresponds;
45
46foreign_type_and_impl_send_sync! {
47 type CType = ffi::ASN1_GENERALIZEDTIME;
48 fn drop = ffi::ASN1_GENERALIZEDTIME_free;
49
50 pub struct Asn1GeneralizedTime;
62}
63
64impl fmt::Display for Asn1GeneralizedTimeRef {
65 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66 let bio = MemBio::new().ok();
67 let msg = bio
68 .as_ref()
69 .and_then(|mem_bio| unsafe {
70 cvt(ffi::ASN1_GENERALIZEDTIME_print(
71 mem_bio.as_ptr(),
72 self.as_ptr(),
73 ))
74 .ok()?;
75 str::from_utf8(mem_bio.get_buf()).ok()
76 })
77 .unwrap_or("error");
78 f.write_str(msg)
79 }
80}
81
82#[derive(Debug, Copy, Clone, PartialEq, Eq)]
84pub struct Asn1Type(c_int);
85
86#[allow(missing_docs)] impl Asn1Type {
88 pub const EOC: Asn1Type = Asn1Type(ffi::V_ASN1_EOC);
89
90 pub const BOOLEAN: Asn1Type = Asn1Type(ffi::V_ASN1_BOOLEAN);
91
92 pub const INTEGER: Asn1Type = Asn1Type(ffi::V_ASN1_INTEGER);
93
94 pub const BIT_STRING: Asn1Type = Asn1Type(ffi::V_ASN1_BIT_STRING);
95
96 pub const OCTET_STRING: Asn1Type = Asn1Type(ffi::V_ASN1_OCTET_STRING);
97
98 pub const NULL: Asn1Type = Asn1Type(ffi::V_ASN1_NULL);
99
100 pub const OBJECT: Asn1Type = Asn1Type(ffi::V_ASN1_OBJECT);
101
102 pub const OBJECT_DESCRIPTOR: Asn1Type = Asn1Type(ffi::V_ASN1_OBJECT_DESCRIPTOR);
103
104 pub const EXTERNAL: Asn1Type = Asn1Type(ffi::V_ASN1_EXTERNAL);
105
106 pub const REAL: Asn1Type = Asn1Type(ffi::V_ASN1_REAL);
107
108 pub const ENUMERATED: Asn1Type = Asn1Type(ffi::V_ASN1_ENUMERATED);
109
110 pub const UTF8STRING: Asn1Type = Asn1Type(ffi::V_ASN1_UTF8STRING);
111
112 pub const SEQUENCE: Asn1Type = Asn1Type(ffi::V_ASN1_SEQUENCE);
113
114 pub const SET: Asn1Type = Asn1Type(ffi::V_ASN1_SET);
115
116 pub const NUMERICSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_NUMERICSTRING);
117
118 pub const PRINTABLESTRING: Asn1Type = Asn1Type(ffi::V_ASN1_PRINTABLESTRING);
119
120 pub const T61STRING: Asn1Type = Asn1Type(ffi::V_ASN1_T61STRING);
121
122 pub const TELETEXSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_TELETEXSTRING);
123
124 pub const VIDEOTEXSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_VIDEOTEXSTRING);
125
126 pub const IA5STRING: Asn1Type = Asn1Type(ffi::V_ASN1_IA5STRING);
127
128 pub const UTCTIME: Asn1Type = Asn1Type(ffi::V_ASN1_UTCTIME);
129
130 pub const GENERALIZEDTIME: Asn1Type = Asn1Type(ffi::V_ASN1_GENERALIZEDTIME);
131
132 pub const GRAPHICSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_GRAPHICSTRING);
133
134 pub const ISO64STRING: Asn1Type = Asn1Type(ffi::V_ASN1_ISO64STRING);
135
136 pub const VISIBLESTRING: Asn1Type = Asn1Type(ffi::V_ASN1_VISIBLESTRING);
137
138 pub const GENERALSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_GENERALSTRING);
139
140 pub const UNIVERSALSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_UNIVERSALSTRING);
141
142 pub const BMPSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_BMPSTRING);
143
144 #[must_use]
146 pub fn from_raw(value: c_int) -> Self {
147 Asn1Type(value)
148 }
149
150 #[must_use]
152 pub fn as_raw(&self) -> c_int {
153 self.0
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Hash)]
165pub struct TimeDiff {
166 pub days: c_int,
168 pub secs: c_int,
172}
173
174foreign_type_and_impl_send_sync! {
175 type CType = ffi::ASN1_TIME;
176 fn drop = ffi::ASN1_TIME_free;
177 pub struct Asn1Time;
188}
189
190impl Asn1TimeRef {
191 #[corresponds(ASN1_TIME_diff)]
193 pub fn diff(&self, compare: &Self) -> Result<TimeDiff, ErrorStack> {
194 let mut days = 0;
195 let mut secs = 0;
196 let other = compare.as_ptr();
197
198 let err = unsafe { ffi::ASN1_TIME_diff(&mut days, &mut secs, self.as_ptr(), other) };
199
200 match err {
201 0 => Err(ErrorStack::get()),
202 _ => Ok(TimeDiff { days, secs }),
203 }
204 }
205
206 #[corresponds(ASN1_TIME_compare)]
208 pub fn compare(&self, other: &Self) -> Result<Ordering, ErrorStack> {
209 let d = self.diff(other)?;
210 if d.days > 0 || d.secs > 0 {
211 return Ok(Ordering::Less);
212 }
213 if d.days < 0 || d.secs < 0 {
214 return Ok(Ordering::Greater);
215 }
216
217 Ok(Ordering::Equal)
218 }
219}
220
221impl PartialEq for Asn1TimeRef {
222 fn eq(&self, other: &Asn1TimeRef) -> bool {
223 self.diff(other)
224 .map(|t| t.days == 0 && t.secs == 0)
225 .unwrap_or(false)
226 }
227}
228
229impl PartialEq<Asn1Time> for Asn1TimeRef {
230 fn eq(&self, other: &Asn1Time) -> bool {
231 self.diff(other)
232 .map(|t| t.days == 0 && t.secs == 0)
233 .unwrap_or(false)
234 }
235}
236
237impl PartialEq<Asn1Time> for &Asn1TimeRef {
238 fn eq(&self, other: &Asn1Time) -> bool {
239 self.diff(other)
240 .map(|t| t.days == 0 && t.secs == 0)
241 .unwrap_or(false)
242 }
243}
244
245impl PartialOrd for Asn1TimeRef {
246 fn partial_cmp(&self, other: &Asn1TimeRef) -> Option<Ordering> {
247 self.compare(other).ok()
248 }
249}
250
251impl PartialOrd<Asn1Time> for Asn1TimeRef {
252 fn partial_cmp(&self, other: &Asn1Time) -> Option<Ordering> {
253 self.compare(other).ok()
254 }
255}
256
257impl PartialOrd<Asn1Time> for &Asn1TimeRef {
258 fn partial_cmp(&self, other: &Asn1Time) -> Option<Ordering> {
259 self.compare(other).ok()
260 }
261}
262
263impl fmt::Display for Asn1TimeRef {
264 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
265 unsafe {
266 let mem_bio = match MemBio::new() {
267 Err(_) => return f.write_str("error"),
268 Ok(m) => m,
269 };
270 let print_result = cvt(ffi::ASN1_TIME_print(mem_bio.as_ptr(), self.as_ptr()));
271 match print_result {
272 Err(_) => f.write_str("error"),
273 Ok(_) => f.write_str(str::from_utf8_unchecked(mem_bio.get_buf())),
274 }
275 }
276 }
277}
278
279impl fmt::Debug for Asn1TimeRef {
280 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
281 f.write_str(&self.to_string())
282 }
283}
284
285impl Asn1Time {
286 #[corresponds(ASN1_TIME_new)]
287 fn new() -> Result<Asn1Time, ErrorStack> {
288 ffi::init();
289
290 unsafe {
291 let handle = cvt_p(ffi::ASN1_TIME_new())?;
292 Ok(Asn1Time::from_ptr(handle))
293 }
294 }
295
296 #[corresponds(X509_gmtime_adj)]
297 fn from_period(period: c_long) -> Result<Asn1Time, ErrorStack> {
298 ffi::init();
299
300 unsafe {
301 let handle = cvt_p(ffi::X509_gmtime_adj(ptr::null_mut(), period))?;
302 Ok(Asn1Time::from_ptr(handle))
303 }
304 }
305
306 pub fn days_from_now(days: u32) -> Result<Asn1Time, ErrorStack> {
308 Self::from_period((days * 60 * 60 * 24) as _)
310 }
311
312 #[corresponds(ASN1_TIME_set)]
314 pub fn from_unix(time: time_t) -> Result<Asn1Time, ErrorStack> {
315 ffi::init();
316
317 unsafe {
318 #[allow(clippy::useless_conversion)]
321 let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time.into()))?;
322 Ok(Asn1Time::from_ptr(handle))
323 }
324 }
325
326 #[corresponds(ASN1_TIME_set_string)]
328 #[allow(clippy::should_implement_trait)]
329 pub fn from_str(s: &str) -> Result<Asn1Time, ErrorStack> {
330 unsafe {
331 let s = CString::new(s).map_err(ErrorStack::internal_error)?;
332
333 let time = Asn1Time::new()?;
334 cvt(ffi::ASN1_TIME_set_string(time.as_ptr(), s.as_ptr()))?;
335
336 Ok(time)
337 }
338 }
339}
340
341impl PartialEq for Asn1Time {
342 fn eq(&self, other: &Asn1Time) -> bool {
343 self.diff(other)
344 .map(|t| t.days == 0 && t.secs == 0)
345 .unwrap_or(false)
346 }
347}
348
349impl PartialEq<Asn1TimeRef> for Asn1Time {
350 fn eq(&self, other: &Asn1TimeRef) -> bool {
351 self.diff(other)
352 .map(|t| t.days == 0 && t.secs == 0)
353 .unwrap_or(false)
354 }
355}
356
357impl<'a> PartialEq<&'a Asn1TimeRef> for Asn1Time {
358 fn eq(&self, other: &&'a Asn1TimeRef) -> bool {
359 self.diff(other)
360 .map(|t| t.days == 0 && t.secs == 0)
361 .unwrap_or(false)
362 }
363}
364
365impl PartialOrd for Asn1Time {
366 fn partial_cmp(&self, other: &Asn1Time) -> Option<Ordering> {
367 self.compare(other).ok()
368 }
369}
370
371impl PartialOrd<Asn1TimeRef> for Asn1Time {
372 fn partial_cmp(&self, other: &Asn1TimeRef) -> Option<Ordering> {
373 self.compare(other).ok()
374 }
375}
376
377impl<'a> PartialOrd<&'a Asn1TimeRef> for Asn1Time {
378 fn partial_cmp(&self, other: &&'a Asn1TimeRef) -> Option<Ordering> {
379 self.compare(other).ok()
380 }
381}
382
383foreign_type_and_impl_send_sync! {
384 type CType = ffi::ASN1_STRING;
385 fn drop = ffi::ASN1_STRING_free;
386 pub struct Asn1String;
394}
395
396impl Asn1StringRef {
397 #[corresponds(ASN1_STRING_to_UTF8)]
403 pub fn as_utf8(&self) -> Result<OpensslString, ErrorStack> {
404 unsafe {
405 let mut ptr = ptr::null_mut();
406 let len = ffi::ASN1_STRING_to_UTF8(&mut ptr, self.as_ptr());
407 if len < 0 {
408 return Err(ErrorStack::get());
409 }
410
411 Ok(OpensslString::from_ptr(ptr.cast()))
412 }
413 }
414
415 #[corresponds(ASN1_STRING_get0_data)]
422 #[must_use]
423 pub fn as_slice(&self) -> &[u8] {
424 unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr()), self.len()) }
425 }
426
427 #[corresponds(ASN1_STRING_length)]
429 #[must_use]
430 pub fn len(&self) -> usize {
431 unsafe { ffi::ASN1_STRING_length(self.as_ptr()) as usize }
432 }
433
434 #[must_use]
436 pub fn is_empty(&self) -> bool {
437 self.len() == 0
438 }
439}
440
441impl fmt::Debug for Asn1StringRef {
442 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
443 match self.as_utf8() {
444 Ok(openssl_string) => openssl_string.fmt(fmt),
445 Err(_) => fmt.write_str("error"),
446 }
447 }
448}
449
450foreign_type_and_impl_send_sync! {
451 type CType = ffi::ASN1_INTEGER;
452 fn drop = ffi::ASN1_INTEGER_free;
453
454 pub struct Asn1Integer;
464}
465
466impl Asn1Integer {
467 pub fn from_bn(bn: &BigNumRef) -> Result<Self, ErrorStack> {
475 bn.to_asn1_integer()
476 }
477}
478
479impl Asn1IntegerRef {
480 #[allow(clippy::unnecessary_cast)]
481 #[allow(missing_docs)]
482 #[deprecated(since = "0.10.6", note = "use to_bn instead")]
483 #[must_use]
484 pub fn get(&self) -> i64 {
485 unsafe { crate::ffi::ASN1_INTEGER_get(self.as_ptr()) as i64 }
486 }
487
488 #[corresponds(ASN1_INTEGER_to_BN)]
490 pub fn to_bn(&self) -> Result<BigNum, ErrorStack> {
491 unsafe {
492 cvt_p(crate::ffi::ASN1_INTEGER_to_BN(
493 self.as_ptr(),
494 ptr::null_mut(),
495 ))
496 .map(|p| BigNum::from_ptr(p))
497 }
498 }
499
500 #[corresponds(ASN1_INTEGER_set)]
505 pub fn set(&mut self, value: i32) -> Result<(), ErrorStack> {
506 unsafe {
507 cvt(crate::ffi::ASN1_INTEGER_set(
508 self.as_ptr(),
509 c_long::from(value),
510 ))
511 }
512 }
513}
514
515foreign_type_and_impl_send_sync! {
516 type CType = ffi::ASN1_BIT_STRING;
517 fn drop = ffi::ASN1_BIT_STRING_free;
518 pub struct Asn1BitString;
525}
526
527impl Asn1BitStringRef {
528 #[corresponds(ASN1_STRING_get0_data)]
530 #[must_use]
531 pub fn as_slice(&self) -> &[u8] {
532 unsafe {
533 let ptr = ASN1_STRING_get0_data(self.as_ptr().cast());
534 if ptr.is_null() {
535 return &[];
536 }
537 slice::from_raw_parts(ptr, self.len())
538 }
539 }
540
541 #[corresponds(ASN1_STRING_get0_data)]
543 #[must_use]
544 pub fn to_str(&self) -> Option<&str> {
545 str::from_utf8(self.as_slice()).ok()
546 }
547
548 #[corresponds(ASN1_STRING_length)]
550 #[must_use]
551 pub fn len(&self) -> usize {
552 unsafe { ffi::ASN1_STRING_length(self.as_ptr().cast_const()) as usize }
553 }
554
555 #[must_use]
557 pub fn is_empty(&self) -> bool {
558 self.len() == 0
559 }
560}
561
562foreign_type_and_impl_send_sync! {
563 type CType = ffi::ASN1_OBJECT;
564 fn drop = ffi::ASN1_OBJECT_free;
565
566 pub struct Asn1Object;
580}
581
582impl Stackable for Asn1Object {
583 type StackType = ffi::stack_st_ASN1_OBJECT;
584}
585
586impl Asn1Object {
587 #[corresponds(OBJ_txt2obj)]
589 #[allow(clippy::should_implement_trait)]
590 pub fn from_str(txt: &str) -> Result<Asn1Object, ErrorStack> {
591 unsafe {
592 ffi::init();
593 let txt = CString::new(txt).map_err(ErrorStack::internal_error)?;
594 let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr(), 0))?;
595 Ok(Asn1Object::from_ptr(obj))
596 }
597 }
598}
599
600impl Asn1ObjectRef {
601 #[must_use]
603 pub fn nid(&self) -> Nid {
604 unsafe { Nid::from_raw(ffi::OBJ_obj2nid(self.as_ptr())) }
605 }
606}
607
608impl fmt::Display for Asn1ObjectRef {
609 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
610 unsafe {
611 let mut buf = [0u8; 80];
612 let len = ffi::OBJ_obj2txt(
613 buf.as_mut_ptr().cast(),
614 buf.len() as c_int,
615 self.as_ptr(),
616 0,
617 );
618 fmt.write_str(
619 buf.get(..len as usize)
620 .and_then(|s| str::from_utf8(s).ok())
621 .unwrap_or("error"),
622 )
623 }
624 }
625}
626
627impl fmt::Debug for Asn1ObjectRef {
628 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
629 fmt.write_str(self.to_string().as_str())
630 }
631}
632
633use crate::ffi::ASN1_STRING_get0_data;
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638
639 use crate::bn::BigNum;
640 use crate::nid::Nid;
641
642 #[test]
644 fn bn_cvt() {
645 fn roundtrip(bn: BigNum) {
646 let large = Asn1Integer::from_bn(&bn).unwrap();
647 assert_eq!(large.to_bn().unwrap(), bn);
648 }
649
650 roundtrip(BigNum::from_dec_str("1000000000000000000000000000000000").unwrap());
651 roundtrip(-BigNum::from_dec_str("1000000000000000000000000000000000").unwrap());
652 roundtrip(BigNum::from_u32(1234).unwrap());
653 roundtrip(-BigNum::from_u32(1234).unwrap());
654 }
655
656 #[test]
657 fn time_from_str() {
658 Asn1Time::from_str("99991231235959Z").unwrap();
659 }
660
661 #[test]
662 fn time_from_unix() {
663 let t = Asn1Time::from_unix(0).unwrap();
664 assert_eq!("Jan 1 00:00:00 1970 GMT", t.to_string());
665 }
666
667 #[test]
668 fn time_eq() {
669 let a = Asn1Time::from_str("99991231235959Z").unwrap();
670 let b = Asn1Time::from_str("99991231235959Z").unwrap();
671 let c = Asn1Time::from_str("99991231235958Z").unwrap();
672 let a_ref = a.as_ref();
673 let b_ref = b.as_ref();
674 let c_ref = c.as_ref();
675 assert!(a == b);
676 assert!(a != c);
677 assert!(a == b_ref);
678 assert!(a != c_ref);
679 assert!(b_ref == a);
680 assert!(c_ref != a);
681 assert!(a_ref == b_ref);
682 assert!(a_ref != c_ref);
683 }
684
685 #[test]
686 fn time_ord() {
687 let a = Asn1Time::from_str("99991231235959Z").unwrap();
688 let b = Asn1Time::from_str("99991231235959Z").unwrap();
689 let c = Asn1Time::from_str("99991231235958Z").unwrap();
690 let a_ref = a.as_ref();
691 let b_ref = b.as_ref();
692 let c_ref = c.as_ref();
693 assert!(a >= b);
694 assert!(a > c);
695 assert!(b <= a);
696 assert!(c < a);
697
698 assert!(a_ref >= b);
699 assert!(a_ref > c);
700 assert!(b_ref <= a);
701 assert!(c_ref < a);
702
703 assert!(a >= b_ref);
704 assert!(a > c_ref);
705 assert!(b <= a_ref);
706 assert!(c < a_ref);
707
708 assert!(a_ref >= b_ref);
709 assert!(a_ref > c_ref);
710 assert!(b_ref <= a_ref);
711 assert!(c_ref < a_ref);
712 }
713
714 #[test]
715 fn object_from_str() {
716 let object = Asn1Object::from_str("2.16.840.1.101.3.4.2.1").unwrap();
717 assert_eq!(object.nid(), Nid::SHA256);
718 }
719
720 #[test]
721 fn object_from_str_with_invalid_input() {
722 Asn1Object::from_str("NOT AN OID")
723 .map(|object| object.to_string())
724 .expect_err("parsing invalid OID should fail");
725 }
726}