1#![deny(missing_docs)]
2
3use crate::ffi;
28use foreign_types::{ForeignType, ForeignTypeRef};
29use libc::{c_char, 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};
44
45foreign_type_and_impl_send_sync! {
46 type CType = ffi::ASN1_GENERALIZEDTIME;
47 fn drop = ffi::ASN1_GENERALIZEDTIME_free;
48
49 pub struct Asn1GeneralizedTime;
61}
62
63impl fmt::Display for Asn1GeneralizedTimeRef {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 unsafe {
66 let mem_bio = match MemBio::new() {
67 Err(_) => return f.write_str("error"),
68 Ok(m) => m,
69 };
70 let print_result = cvt(ffi::ASN1_GENERALIZEDTIME_print(
71 mem_bio.as_ptr(),
72 self.as_ptr(),
73 ));
74 match print_result {
75 Err(_) => f.write_str("error"),
76 Ok(_) => f.write_str(str::from_utf8_unchecked(mem_bio.get_buf())),
77 }
78 }
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 pub fn from_raw(value: c_int) -> Self {
146 Asn1Type(value)
147 }
148
149 pub fn as_raw(&self) -> c_int {
151 self.0
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Hash)]
163pub struct TimeDiff {
164 pub days: c_int,
166 pub secs: c_int,
170}
171
172foreign_type_and_impl_send_sync! {
173 type CType = ffi::ASN1_TIME;
174 fn drop = ffi::ASN1_TIME_free;
175 pub struct Asn1Time;
186}
187
188impl Asn1TimeRef {
189 pub fn diff(&self, compare: &Self) -> Result<TimeDiff, ErrorStack> {
195 let mut days = 0;
196 let mut secs = 0;
197 let other = compare.as_ptr();
198
199 let err = unsafe { ffi::ASN1_TIME_diff(&mut days, &mut secs, self.as_ptr(), other) };
200
201 match err {
202 0 => Err(ErrorStack::get()),
203 _ => Ok(TimeDiff { days, secs }),
204 }
205 }
206
207 pub fn compare(&self, other: &Self) -> Result<Ordering, ErrorStack> {
215 let d = self.diff(other)?;
216 if d.days > 0 || d.secs > 0 {
217 return Ok(Ordering::Less);
218 }
219 if d.days < 0 || d.secs < 0 {
220 return Ok(Ordering::Greater);
221 }
222
223 Ok(Ordering::Equal)
224 }
225}
226
227impl PartialEq for Asn1TimeRef {
228 fn eq(&self, other: &Asn1TimeRef) -> bool {
229 self.diff(other)
230 .map(|t| t.days == 0 && t.secs == 0)
231 .unwrap_or(false)
232 }
233}
234
235impl PartialEq<Asn1Time> for Asn1TimeRef {
236 fn eq(&self, other: &Asn1Time) -> bool {
237 self.diff(other)
238 .map(|t| t.days == 0 && t.secs == 0)
239 .unwrap_or(false)
240 }
241}
242
243impl<'a> PartialEq<Asn1Time> for &'a Asn1TimeRef {
244 fn eq(&self, other: &Asn1Time) -> bool {
245 self.diff(other)
246 .map(|t| t.days == 0 && t.secs == 0)
247 .unwrap_or(false)
248 }
249}
250
251impl PartialOrd for Asn1TimeRef {
252 fn partial_cmp(&self, other: &Asn1TimeRef) -> 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<'a> PartialOrd<Asn1Time> for &'a Asn1TimeRef {
264 fn partial_cmp(&self, other: &Asn1Time) -> Option<Ordering> {
265 self.compare(other).ok()
266 }
267}
268
269impl fmt::Display for Asn1TimeRef {
270 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271 unsafe {
272 let mem_bio = match MemBio::new() {
273 Err(_) => return f.write_str("error"),
274 Ok(m) => m,
275 };
276 let print_result = cvt(ffi::ASN1_TIME_print(mem_bio.as_ptr(), self.as_ptr()));
277 match print_result {
278 Err(_) => f.write_str("error"),
279 Ok(_) => f.write_str(str::from_utf8_unchecked(mem_bio.get_buf())),
280 }
281 }
282 }
283}
284
285impl fmt::Debug for Asn1TimeRef {
286 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
287 f.write_str(&self.to_string())
288 }
289}
290
291impl Asn1Time {
292 fn new() -> Result<Asn1Time, ErrorStack> {
293 ffi::init();
294
295 unsafe {
296 let handle = cvt_p(ffi::ASN1_TIME_new())?;
297 Ok(Asn1Time::from_ptr(handle))
298 }
299 }
300
301 fn from_period(period: c_long) -> Result<Asn1Time, ErrorStack> {
302 ffi::init();
303
304 unsafe {
305 let handle = cvt_p(ffi::X509_gmtime_adj(ptr::null_mut(), period))?;
306 Ok(Asn1Time::from_ptr(handle))
307 }
308 }
309
310 pub fn days_from_now(days: u32) -> Result<Asn1Time, ErrorStack> {
312 Asn1Time::from_period(days as c_long * 60 * 60 * 24)
313 }
314
315 pub fn from_unix(time: time_t) -> Result<Asn1Time, ErrorStack> {
317 ffi::init();
318
319 unsafe {
320 let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time))?;
321 Ok(Asn1Time::from_ptr(handle))
322 }
323 }
324
325 #[allow(clippy::should_implement_trait)]
331 pub fn from_str(s: &str) -> Result<Asn1Time, ErrorStack> {
332 unsafe {
333 let s = CString::new(s).unwrap();
334
335 let time = Asn1Time::new()?;
336 cvt(ffi::ASN1_TIME_set_string(time.as_ptr(), s.as_ptr()))?;
337
338 Ok(time)
339 }
340 }
341}
342
343impl PartialEq for Asn1Time {
344 fn eq(&self, other: &Asn1Time) -> bool {
345 self.diff(other)
346 .map(|t| t.days == 0 && t.secs == 0)
347 .unwrap_or(false)
348 }
349}
350
351impl PartialEq<Asn1TimeRef> for Asn1Time {
352 fn eq(&self, other: &Asn1TimeRef) -> bool {
353 self.diff(other)
354 .map(|t| t.days == 0 && t.secs == 0)
355 .unwrap_or(false)
356 }
357}
358
359impl<'a> PartialEq<&'a Asn1TimeRef> for Asn1Time {
360 fn eq(&self, other: &&'a Asn1TimeRef) -> bool {
361 self.diff(other)
362 .map(|t| t.days == 0 && t.secs == 0)
363 .unwrap_or(false)
364 }
365}
366
367impl PartialOrd for Asn1Time {
368 fn partial_cmp(&self, other: &Asn1Time) -> Option<Ordering> {
369 self.compare(other).ok()
370 }
371}
372
373impl PartialOrd<Asn1TimeRef> for Asn1Time {
374 fn partial_cmp(&self, other: &Asn1TimeRef) -> Option<Ordering> {
375 self.compare(other).ok()
376 }
377}
378
379impl<'a> PartialOrd<&'a Asn1TimeRef> for Asn1Time {
380 fn partial_cmp(&self, other: &&'a Asn1TimeRef) -> Option<Ordering> {
381 self.compare(other).ok()
382 }
383}
384
385foreign_type_and_impl_send_sync! {
386 type CType = ffi::ASN1_STRING;
387 fn drop = ffi::ASN1_STRING_free;
388 pub struct Asn1String;
396}
397
398impl Asn1StringRef {
399 pub fn as_utf8(&self) -> Result<OpensslString, ErrorStack> {
405 unsafe {
406 let mut ptr = ptr::null_mut();
407 let len = ffi::ASN1_STRING_to_UTF8(&mut ptr, self.as_ptr());
408 if len < 0 {
409 return Err(ErrorStack::get());
410 }
411
412 Ok(OpensslString::from_ptr(ptr as *mut c_char))
413 }
414 }
415
416 pub fn as_slice(&self) -> &[u8] {
423 unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr()), self.len()) }
424 }
425
426 pub fn len(&self) -> usize {
428 unsafe { ffi::ASN1_STRING_length(self.as_ptr()) as usize }
429 }
430
431 pub fn is_empty(&self) -> bool {
433 self.len() == 0
434 }
435}
436
437impl fmt::Debug for Asn1StringRef {
438 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
439 match self.as_utf8() {
440 Ok(openssl_string) => openssl_string.fmt(fmt),
441 Err(_) => fmt.write_str("error"),
442 }
443 }
444}
445
446foreign_type_and_impl_send_sync! {
447 type CType = ffi::ASN1_INTEGER;
448 fn drop = ffi::ASN1_INTEGER_free;
449
450 pub struct Asn1Integer;
460}
461
462impl Asn1Integer {
463 pub fn from_bn(bn: &BigNumRef) -> Result<Self, ErrorStack> {
471 bn.to_asn1_integer()
472 }
473}
474
475impl Asn1IntegerRef {
476 #[allow(clippy::unnecessary_cast)]
477 #[allow(missing_docs)]
478 #[deprecated(since = "0.10.6", note = "use to_bn instead")]
479 pub fn get(&self) -> i64 {
480 unsafe { crate::ffi::ASN1_INTEGER_get(self.as_ptr()) as i64 }
481 }
482
483 pub fn to_bn(&self) -> Result<BigNum, ErrorStack> {
489 unsafe {
490 cvt_p(crate::ffi::ASN1_INTEGER_to_BN(
491 self.as_ptr(),
492 ptr::null_mut(),
493 ))
494 .map(|p| BigNum::from_ptr(p))
495 }
496 }
497
498 pub fn set(&mut self, value: i32) -> Result<(), ErrorStack> {
506 unsafe { cvt(crate::ffi::ASN1_INTEGER_set(self.as_ptr(), value as c_long)).map(|_| ()) }
507 }
508}
509
510foreign_type_and_impl_send_sync! {
511 type CType = ffi::ASN1_BIT_STRING;
512 fn drop = ffi::ASN1_BIT_STRING_free;
513 pub struct Asn1BitString;
520}
521
522impl Asn1BitStringRef {
523 pub fn as_slice(&self) -> &[u8] {
525 unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr() as *mut _), self.len()) }
526 }
527
528 pub fn len(&self) -> usize {
530 unsafe { ffi::ASN1_STRING_length(self.as_ptr() as *const _) as usize }
531 }
532
533 pub fn is_empty(&self) -> bool {
535 self.len() == 0
536 }
537}
538
539foreign_type_and_impl_send_sync! {
540 type CType = ffi::ASN1_OBJECT;
541 fn drop = ffi::ASN1_OBJECT_free;
542
543 pub struct Asn1Object;
557}
558
559impl Stackable for Asn1Object {
560 type StackType = ffi::stack_st_ASN1_OBJECT;
561}
562
563impl Asn1Object {
564 #[allow(clippy::should_implement_trait)]
571 pub fn from_str(txt: &str) -> Result<Asn1Object, ErrorStack> {
572 unsafe {
573 ffi::init();
574 let txt = CString::new(txt).unwrap();
575 let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr() as *const _, 0))?;
576 Ok(Asn1Object::from_ptr(obj))
577 }
578 }
579}
580
581impl Asn1ObjectRef {
582 pub fn nid(&self) -> Nid {
584 unsafe { Nid::from_raw(ffi::OBJ_obj2nid(self.as_ptr())) }
585 }
586}
587
588impl fmt::Display for Asn1ObjectRef {
589 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
590 unsafe {
591 let mut buf = [0; 80];
592 let len = ffi::OBJ_obj2txt(
593 buf.as_mut_ptr() as *mut _,
594 buf.len() as c_int,
595 self.as_ptr(),
596 0,
597 );
598 match str::from_utf8(&buf[..len as usize]) {
599 Err(_) => fmt.write_str("error"),
600 Ok(s) => fmt.write_str(s),
601 }
602 }
603 }
604}
605
606impl fmt::Debug for Asn1ObjectRef {
607 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
608 fmt.write_str(self.to_string().as_str())
609 }
610}
611
612use crate::ffi::ASN1_STRING_get0_data;
613
614#[cfg(test)]
615mod tests {
616 use super::*;
617
618 use crate::bn::BigNum;
619 use crate::nid::Nid;
620
621 #[test]
623 fn bn_cvt() {
624 fn roundtrip(bn: BigNum) {
625 let large = Asn1Integer::from_bn(&bn).unwrap();
626 assert_eq!(large.to_bn().unwrap(), bn);
627 }
628
629 roundtrip(BigNum::from_dec_str("1000000000000000000000000000000000").unwrap());
630 roundtrip(-BigNum::from_dec_str("1000000000000000000000000000000000").unwrap());
631 roundtrip(BigNum::from_u32(1234).unwrap());
632 roundtrip(-BigNum::from_u32(1234).unwrap());
633 }
634
635 #[test]
636 fn time_from_str() {
637 Asn1Time::from_str("99991231235959Z").unwrap();
638 }
639
640 #[test]
641 fn time_from_unix() {
642 let t = Asn1Time::from_unix(0).unwrap();
643 assert_eq!("Jan 1 00:00:00 1970 GMT", t.to_string());
644 }
645
646 #[test]
647 fn time_eq() {
648 let a = Asn1Time::from_str("99991231235959Z").unwrap();
649 let b = Asn1Time::from_str("99991231235959Z").unwrap();
650 let c = Asn1Time::from_str("99991231235958Z").unwrap();
651 let a_ref = a.as_ref();
652 let b_ref = b.as_ref();
653 let c_ref = c.as_ref();
654 assert!(a == b);
655 assert!(a != c);
656 assert!(a == b_ref);
657 assert!(a != c_ref);
658 assert!(b_ref == a);
659 assert!(c_ref != a);
660 assert!(a_ref == b_ref);
661 assert!(a_ref != c_ref);
662 }
663
664 #[test]
665 fn time_ord() {
666 let a = Asn1Time::from_str("99991231235959Z").unwrap();
667 let b = Asn1Time::from_str("99991231235959Z").unwrap();
668 let c = Asn1Time::from_str("99991231235958Z").unwrap();
669 let a_ref = a.as_ref();
670 let b_ref = b.as_ref();
671 let c_ref = c.as_ref();
672 assert!(a >= b);
673 assert!(a > c);
674 assert!(b <= a);
675 assert!(c < a);
676
677 assert!(a_ref >= b);
678 assert!(a_ref > c);
679 assert!(b_ref <= a);
680 assert!(c_ref < a);
681
682 assert!(a >= b_ref);
683 assert!(a > c_ref);
684 assert!(b <= a_ref);
685 assert!(c < a_ref);
686
687 assert!(a_ref >= b_ref);
688 assert!(a_ref > c_ref);
689 assert!(b_ref <= a_ref);
690 assert!(c_ref < a_ref);
691 }
692
693 #[test]
694 fn object_from_str() {
695 let object = Asn1Object::from_str("2.16.840.1.101.3.4.2.1").unwrap();
696 assert_eq!(object.nid(), Nid::SHA256);
697 }
698
699 #[test]
700 fn object_from_str_with_invalid_input() {
701 Asn1Object::from_str("NOT AN OID")
702 .map(|object| object.to_string())
703 .expect_err("parsing invalid OID should fail");
704 }
705}