diff options
Diffstat (limited to 'vendor/openssl/src/asn1.rs')
-rw-r--r-- | vendor/openssl/src/asn1.rs | 911 |
1 files changed, 911 insertions, 0 deletions
diff --git a/vendor/openssl/src/asn1.rs b/vendor/openssl/src/asn1.rs new file mode 100644 index 0000000..801310d --- /dev/null +++ b/vendor/openssl/src/asn1.rs @@ -0,0 +1,911 @@ +#![deny(missing_docs)] + +//! Defines the format of certificates +//! +//! This module is used by [`x509`] and other certificate building functions +//! to describe time, strings, and objects. +//! +//! Abstract Syntax Notation One is an interface description language. +//! The specification comes from [X.208] by OSI, and rewritten in X.680. +//! ASN.1 describes properties of an object with a type set. Those types +//! can be atomic, structured, choice, and other (CHOICE and ANY). These +//! types are expressed as a number and the assignment operator ::= gives +//! the type a name. +//! +//! The implementation here provides a subset of the ASN.1 types that OpenSSL +//! uses, especially in the properties of a certificate used in HTTPS. +//! +//! [X.208]: https://www.itu.int/rec/T-REC-X.208-198811-W/en +//! [`x509`]: ../x509/struct.X509Builder.html +//! +//! ## Examples +//! +//! ``` +//! use openssl::asn1::Asn1Time; +//! let tomorrow = Asn1Time::days_from_now(1); +//! ``` +use cfg_if::cfg_if; +use foreign_types::{ForeignType, ForeignTypeRef}; +use libc::{c_char, c_int, c_long, time_t}; +use std::cmp::Ordering; +use std::convert::TryInto; +use std::ffi::CString; +use std::fmt; +use std::ptr; +use std::slice; +use std::str; + +use crate::bio::MemBio; +use crate::bn::{BigNum, BigNumRef}; +use crate::error::ErrorStack; +use crate::nid::Nid; +use crate::stack::Stackable; +use crate::string::OpensslString; +use crate::{cvt, cvt_p}; +use openssl_macros::corresponds; + +foreign_type_and_impl_send_sync! { + type CType = ffi::ASN1_GENERALIZEDTIME; + fn drop = ffi::ASN1_GENERALIZEDTIME_free; + + /// Non-UTC representation of time + /// + /// If a time can be represented by UTCTime, UTCTime is used + /// otherwise, ASN1_GENERALIZEDTIME is used. This would be, for + /// example outside the year range of 1950-2049. + /// + /// [ASN1_GENERALIZEDTIME_set] documentation from OpenSSL provides + /// further details of implementation. Note: these docs are from the master + /// branch as documentation on the 1.1.0 branch did not include this page. + /// + /// [ASN1_GENERALIZEDTIME_set]: https://www.openssl.org/docs/manmaster/man3/ASN1_GENERALIZEDTIME_set.html + pub struct Asn1GeneralizedTime; + /// Reference to a [`Asn1GeneralizedTime`] + /// + /// [`Asn1GeneralizedTime`]: struct.Asn1GeneralizedTime.html + pub struct Asn1GeneralizedTimeRef; +} + +impl fmt::Display for Asn1GeneralizedTimeRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + unsafe { + let mem_bio = match MemBio::new() { + Err(_) => return f.write_str("error"), + Ok(m) => m, + }; + let print_result = cvt(ffi::ASN1_GENERALIZEDTIME_print( + mem_bio.as_ptr(), + self.as_ptr(), + )); + match print_result { + Err(_) => f.write_str("error"), + Ok(_) => f.write_str(str::from_utf8_unchecked(mem_bio.get_buf())), + } + } + } +} + +/// The type of an ASN.1 value. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Asn1Type(c_int); + +#[allow(missing_docs)] // no need to document the constants +impl Asn1Type { + pub const EOC: Asn1Type = Asn1Type(ffi::V_ASN1_EOC); + + pub const BOOLEAN: Asn1Type = Asn1Type(ffi::V_ASN1_BOOLEAN); + + pub const INTEGER: Asn1Type = Asn1Type(ffi::V_ASN1_INTEGER); + + pub const BIT_STRING: Asn1Type = Asn1Type(ffi::V_ASN1_BIT_STRING); + + pub const OCTET_STRING: Asn1Type = Asn1Type(ffi::V_ASN1_OCTET_STRING); + + pub const NULL: Asn1Type = Asn1Type(ffi::V_ASN1_NULL); + + pub const OBJECT: Asn1Type = Asn1Type(ffi::V_ASN1_OBJECT); + + pub const OBJECT_DESCRIPTOR: Asn1Type = Asn1Type(ffi::V_ASN1_OBJECT_DESCRIPTOR); + + pub const EXTERNAL: Asn1Type = Asn1Type(ffi::V_ASN1_EXTERNAL); + + pub const REAL: Asn1Type = Asn1Type(ffi::V_ASN1_REAL); + + pub const ENUMERATED: Asn1Type = Asn1Type(ffi::V_ASN1_ENUMERATED); + + pub const UTF8STRING: Asn1Type = Asn1Type(ffi::V_ASN1_UTF8STRING); + + pub const SEQUENCE: Asn1Type = Asn1Type(ffi::V_ASN1_SEQUENCE); + + pub const SET: Asn1Type = Asn1Type(ffi::V_ASN1_SET); + + pub const NUMERICSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_NUMERICSTRING); + + pub const PRINTABLESTRING: Asn1Type = Asn1Type(ffi::V_ASN1_PRINTABLESTRING); + + pub const T61STRING: Asn1Type = Asn1Type(ffi::V_ASN1_T61STRING); + + pub const TELETEXSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_TELETEXSTRING); + + pub const VIDEOTEXSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_VIDEOTEXSTRING); + + pub const IA5STRING: Asn1Type = Asn1Type(ffi::V_ASN1_IA5STRING); + + pub const UTCTIME: Asn1Type = Asn1Type(ffi::V_ASN1_UTCTIME); + + pub const GENERALIZEDTIME: Asn1Type = Asn1Type(ffi::V_ASN1_GENERALIZEDTIME); + + pub const GRAPHICSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_GRAPHICSTRING); + + pub const ISO64STRING: Asn1Type = Asn1Type(ffi::V_ASN1_ISO64STRING); + + pub const VISIBLESTRING: Asn1Type = Asn1Type(ffi::V_ASN1_VISIBLESTRING); + + pub const GENERALSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_GENERALSTRING); + + pub const UNIVERSALSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_UNIVERSALSTRING); + + pub const BMPSTRING: Asn1Type = Asn1Type(ffi::V_ASN1_BMPSTRING); + + /// Constructs an `Asn1Type` from a raw OpenSSL value. + pub fn from_raw(value: c_int) -> Self { + Asn1Type(value) + } + + /// Returns the raw OpenSSL value represented by this type. + pub fn as_raw(&self) -> c_int { + self.0 + } +} + +/// Difference between two ASN1 times. +/// +/// This `struct` is created by the [`diff`] method on [`Asn1TimeRef`]. See its +/// documentation for more. +/// +/// [`diff`]: struct.Asn1TimeRef.html#method.diff +/// [`Asn1TimeRef`]: struct.Asn1TimeRef.html +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg(ossl102)] +pub struct TimeDiff { + /// Difference in days + pub days: c_int, + /// Difference in seconds. + /// + /// This is always less than the number of seconds in a day. + pub secs: c_int, +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::ASN1_TIME; + fn drop = ffi::ASN1_TIME_free; + /// Time storage and comparison + /// + /// Asn1Time should be used to store and share time information + /// using certificates. If Asn1Time is set using a string, it must + /// be in either YYMMDDHHMMSSZ, YYYYMMDDHHMMSSZ, or another ASN.1 format. + /// + /// [ASN_TIME_set] documentation at OpenSSL explains the ASN.1 implementation + /// used by OpenSSL. + /// + /// [ASN_TIME_set]: https://www.openssl.org/docs/manmaster/crypto/ASN1_TIME_set.html + pub struct Asn1Time; + /// Reference to an [`Asn1Time`] + /// + /// [`Asn1Time`]: struct.Asn1Time.html + pub struct Asn1TimeRef; +} + +impl Asn1TimeRef { + /// Find difference between two times + #[corresponds(ASN1_TIME_diff)] + #[cfg(ossl102)] + pub fn diff(&self, compare: &Self) -> Result<TimeDiff, ErrorStack> { + let mut days = 0; + let mut secs = 0; + let other = compare.as_ptr(); + + let err = unsafe { ffi::ASN1_TIME_diff(&mut days, &mut secs, self.as_ptr(), other) }; + + match err { + 0 => Err(ErrorStack::get()), + _ => Ok(TimeDiff { days, secs }), + } + } + + /// Compare two times + #[corresponds(ASN1_TIME_compare)] + #[cfg(ossl102)] + pub fn compare(&self, other: &Self) -> Result<Ordering, ErrorStack> { + let d = self.diff(other)?; + if d.days > 0 || d.secs > 0 { + return Ok(Ordering::Less); + } + if d.days < 0 || d.secs < 0 { + return Ok(Ordering::Greater); + } + + Ok(Ordering::Equal) + } +} + +#[cfg(ossl102)] +impl PartialEq for Asn1TimeRef { + fn eq(&self, other: &Asn1TimeRef) -> bool { + self.diff(other) + .map(|t| t.days == 0 && t.secs == 0) + .unwrap_or(false) + } +} + +#[cfg(ossl102)] +impl PartialEq<Asn1Time> for Asn1TimeRef { + fn eq(&self, other: &Asn1Time) -> bool { + self.diff(other) + .map(|t| t.days == 0 && t.secs == 0) + .unwrap_or(false) + } +} + +#[cfg(ossl102)] +impl<'a> PartialEq<Asn1Time> for &'a Asn1TimeRef { + fn eq(&self, other: &Asn1Time) -> bool { + self.diff(other) + .map(|t| t.days == 0 && t.secs == 0) + .unwrap_or(false) + } +} + +#[cfg(ossl102)] +impl PartialOrd for Asn1TimeRef { + fn partial_cmp(&self, other: &Asn1TimeRef) -> Option<Ordering> { + self.compare(other).ok() + } +} + +#[cfg(ossl102)] +impl PartialOrd<Asn1Time> for Asn1TimeRef { + fn partial_cmp(&self, other: &Asn1Time) -> Option<Ordering> { + self.compare(other).ok() + } +} + +#[cfg(ossl102)] +impl<'a> PartialOrd<Asn1Time> for &'a Asn1TimeRef { + fn partial_cmp(&self, other: &Asn1Time) -> Option<Ordering> { + self.compare(other).ok() + } +} + +impl fmt::Display for Asn1TimeRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + unsafe { + let mem_bio = match MemBio::new() { + Err(_) => return f.write_str("error"), + Ok(m) => m, + }; + let print_result = cvt(ffi::ASN1_TIME_print(mem_bio.as_ptr(), self.as_ptr())); + match print_result { + Err(_) => f.write_str("error"), + Ok(_) => f.write_str(str::from_utf8_unchecked(mem_bio.get_buf())), + } + } + } +} + +impl fmt::Debug for Asn1TimeRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.to_string()) + } +} + +impl Asn1Time { + #[corresponds(ASN1_TIME_new)] + fn new() -> Result<Asn1Time, ErrorStack> { + ffi::init(); + + unsafe { + let handle = cvt_p(ffi::ASN1_TIME_new())?; + Ok(Asn1Time::from_ptr(handle)) + } + } + + #[corresponds(X509_gmtime_adj)] + fn from_period(period: c_long) -> Result<Asn1Time, ErrorStack> { + ffi::init(); + + unsafe { + let handle = cvt_p(ffi::X509_gmtime_adj(ptr::null_mut(), period))?; + Ok(Asn1Time::from_ptr(handle)) + } + } + + /// Creates a new time on specified interval in days from now + pub fn days_from_now(days: u32) -> Result<Asn1Time, ErrorStack> { + Asn1Time::from_period(days as c_long * 60 * 60 * 24) + } + + /// Creates a new time from the specified `time_t` value + #[corresponds(ASN1_TIME_set)] + pub fn from_unix(time: time_t) -> Result<Asn1Time, ErrorStack> { + ffi::init(); + + unsafe { + let handle = cvt_p(ffi::ASN1_TIME_set(ptr::null_mut(), time))?; + Ok(Asn1Time::from_ptr(handle)) + } + } + + /// Creates a new time corresponding to the specified ASN1 time string. + #[corresponds(ASN1_TIME_set_string)] + #[allow(clippy::should_implement_trait)] + pub fn from_str(s: &str) -> Result<Asn1Time, ErrorStack> { + unsafe { + let s = CString::new(s).unwrap(); + + let time = Asn1Time::new()?; + cvt(ffi::ASN1_TIME_set_string(time.as_ptr(), s.as_ptr()))?; + + Ok(time) + } + } + + /// Creates a new time corresponding to the specified X509 time string. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(ASN1_TIME_set_string_X509)] + #[cfg(ossl111)] + pub fn from_str_x509(s: &str) -> Result<Asn1Time, ErrorStack> { + unsafe { + let s = CString::new(s).unwrap(); + + let time = Asn1Time::new()?; + cvt(ffi::ASN1_TIME_set_string_X509(time.as_ptr(), s.as_ptr()))?; + + Ok(time) + } + } +} + +#[cfg(ossl102)] +impl PartialEq for Asn1Time { + fn eq(&self, other: &Asn1Time) -> bool { + self.diff(other) + .map(|t| t.days == 0 && t.secs == 0) + .unwrap_or(false) + } +} + +#[cfg(ossl102)] +impl PartialEq<Asn1TimeRef> for Asn1Time { + fn eq(&self, other: &Asn1TimeRef) -> bool { + self.diff(other) + .map(|t| t.days == 0 && t.secs == 0) + .unwrap_or(false) + } +} + +#[cfg(ossl102)] +impl<'a> PartialEq<&'a Asn1TimeRef> for Asn1Time { + fn eq(&self, other: &&'a Asn1TimeRef) -> bool { + self.diff(other) + .map(|t| t.days == 0 && t.secs == 0) + .unwrap_or(false) + } +} + +#[cfg(ossl102)] +impl PartialOrd for Asn1Time { + fn partial_cmp(&self, other: &Asn1Time) -> Option<Ordering> { + self.compare(other).ok() + } +} + +#[cfg(ossl102)] +impl PartialOrd<Asn1TimeRef> for Asn1Time { + fn partial_cmp(&self, other: &Asn1TimeRef) -> Option<Ordering> { + self.compare(other).ok() + } +} + +#[cfg(ossl102)] +impl<'a> PartialOrd<&'a Asn1TimeRef> for Asn1Time { + fn partial_cmp(&self, other: &&'a Asn1TimeRef) -> Option<Ordering> { + self.compare(other).ok() + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::ASN1_STRING; + fn drop = ffi::ASN1_STRING_free; + /// Primary ASN.1 type used by OpenSSL + /// + /// Almost all ASN.1 types in OpenSSL are represented by ASN1_STRING + /// structures. This implementation uses [ASN1_STRING-to_UTF8] to preserve + /// compatibility with Rust's String. + /// + /// [ASN1_STRING-to_UTF8]: https://www.openssl.org/docs/manmaster/crypto/ASN1_STRING_to_UTF8.html + pub struct Asn1String; + /// A reference to an [`Asn1String`]. + pub struct Asn1StringRef; +} + +impl Asn1StringRef { + /// Converts the ASN.1 underlying format to UTF8 + /// + /// ASN.1 strings may utilize UTF-16, ASCII, BMP, or UTF8. This is important to + /// consume the string in a meaningful way without knowing the underlying + /// format. + #[corresponds(ASN1_STRING_to_UTF8)] + pub fn as_utf8(&self) -> Result<OpensslString, ErrorStack> { + unsafe { + let mut ptr = ptr::null_mut(); + let len = ffi::ASN1_STRING_to_UTF8(&mut ptr, self.as_ptr()); + if len < 0 { + return Err(ErrorStack::get()); + } + + Ok(OpensslString::from_ptr(ptr as *mut c_char)) + } + } + + /// Return the string as an array of bytes. + /// + /// The bytes do not directly correspond to UTF-8 encoding. To interact with + /// strings in rust, it is preferable to use [`as_utf8`] + /// + /// [`as_utf8`]: struct.Asn1String.html#method.as_utf8 + #[corresponds(ASN1_STRING_get0_data)] + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr()), self.len()) } + } + + /// Returns the number of bytes in the string. + #[corresponds(ASN1_STRING_length)] + pub fn len(&self) -> usize { + unsafe { ffi::ASN1_STRING_length(self.as_ptr()) as usize } + } + + /// Determines if the string is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl fmt::Debug for Asn1StringRef { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.as_utf8() { + Ok(openssl_string) => openssl_string.fmt(fmt), + Err(_) => fmt.write_str("error"), + } + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::ASN1_INTEGER; + fn drop = ffi::ASN1_INTEGER_free; + + /// Numeric representation + /// + /// Integers in ASN.1 may include BigNum, int64 or uint64. BigNum implementation + /// can be found within [`bn`] module. + /// + /// OpenSSL documentation includes [`ASN1_INTEGER_set`]. + /// + /// [`bn`]: ../bn/index.html + /// [`ASN1_INTEGER_set`]: https://www.openssl.org/docs/manmaster/crypto/ASN1_INTEGER_set.html + pub struct Asn1Integer; + /// A reference to an [`Asn1Integer`]. + pub struct Asn1IntegerRef; +} + +impl Asn1Integer { + /// Converts a bignum to an `Asn1Integer`. + /// + /// Corresponds to [`BN_to_ASN1_INTEGER`]. Also see + /// [`BigNumRef::to_asn1_integer`]. + /// + /// [`BN_to_ASN1_INTEGER`]: https://www.openssl.org/docs/manmaster/crypto/BN_to_ASN1_INTEGER.html + /// [`BigNumRef::to_asn1_integer`]: ../bn/struct.BigNumRef.html#method.to_asn1_integer + pub fn from_bn(bn: &BigNumRef) -> Result<Self, ErrorStack> { + bn.to_asn1_integer() + } +} + +impl Ord for Asn1Integer { + fn cmp(&self, other: &Self) -> Ordering { + Asn1IntegerRef::cmp(self, other) + } +} +impl PartialOrd for Asn1Integer { + fn partial_cmp(&self, other: &Asn1Integer) -> Option<Ordering> { + Some(self.cmp(other)) + } +} +impl Eq for Asn1Integer {} +impl PartialEq for Asn1Integer { + fn eq(&self, other: &Asn1Integer) -> bool { + Asn1IntegerRef::eq(self, other) + } +} + +impl Asn1IntegerRef { + #[allow(missing_docs, clippy::unnecessary_cast)] + #[deprecated(since = "0.10.6", note = "use to_bn instead")] + pub fn get(&self) -> i64 { + unsafe { ffi::ASN1_INTEGER_get(self.as_ptr()) as i64 } + } + + /// Converts the integer to a `BigNum`. + #[corresponds(ASN1_INTEGER_to_BN)] + pub fn to_bn(&self) -> Result<BigNum, ErrorStack> { + unsafe { + cvt_p(ffi::ASN1_INTEGER_to_BN(self.as_ptr(), ptr::null_mut())) + .map(|p| BigNum::from_ptr(p)) + } + } + + /// Sets the ASN.1 value to the value of a signed 32-bit integer, for larger numbers + /// see [`bn`]. + /// + /// [`bn`]: ../bn/struct.BigNumRef.html#method.to_asn1_integer + #[corresponds(ASN1_INTEGER_set)] + pub fn set(&mut self, value: i32) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::ASN1_INTEGER_set(self.as_ptr(), value as c_long)).map(|_| ()) } + } + + /// Creates a new Asn1Integer with the same value. + #[corresponds(ASN1_INTEGER_dup)] + pub fn to_owned(&self) -> Result<Asn1Integer, ErrorStack> { + unsafe { cvt_p(ffi::ASN1_INTEGER_dup(self.as_ptr())).map(|p| Asn1Integer::from_ptr(p)) } + } +} + +impl Ord for Asn1IntegerRef { + fn cmp(&self, other: &Self) -> Ordering { + let res = unsafe { ffi::ASN1_INTEGER_cmp(self.as_ptr(), other.as_ptr()) }; + res.cmp(&0) + } +} +impl PartialOrd for Asn1IntegerRef { + fn partial_cmp(&self, other: &Asn1IntegerRef) -> Option<Ordering> { + Some(self.cmp(other)) + } +} +impl Eq for Asn1IntegerRef {} +impl PartialEq for Asn1IntegerRef { + fn eq(&self, other: &Asn1IntegerRef) -> bool { + self.cmp(other) == Ordering::Equal + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::ASN1_BIT_STRING; + fn drop = ffi::ASN1_BIT_STRING_free; + /// Sequence of bytes + /// + /// Asn1BitString is used in [`x509`] certificates for the signature. + /// The bit string acts as a collection of bytes. + /// + /// [`x509`]: ../x509/struct.X509.html#method.signature + pub struct Asn1BitString; + /// A reference to an [`Asn1BitString`]. + pub struct Asn1BitStringRef; +} + +impl Asn1BitStringRef { + /// Returns the Asn1BitString as a slice. + #[corresponds(ASN1_STRING_get0_data)] + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr() as *mut _), self.len()) } + } + + /// Returns the number of bytes in the string. + #[corresponds(ASN1_STRING_length)] + pub fn len(&self) -> usize { + unsafe { ffi::ASN1_STRING_length(self.as_ptr() as *const _) as usize } + } + + /// Determines if the string is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::ASN1_OCTET_STRING; + fn drop = ffi::ASN1_OCTET_STRING_free; + /// ASN.1 OCTET STRING type + pub struct Asn1OctetString; + /// A reference to an [`Asn1OctetString`]. + pub struct Asn1OctetStringRef; +} + +impl Asn1OctetString { + /// Creates an Asn1OctetString from bytes + pub fn new_from_bytes(value: &[u8]) -> Result<Self, ErrorStack> { + ffi::init(); + unsafe { + let s = cvt_p(ffi::ASN1_OCTET_STRING_new())?; + ffi::ASN1_OCTET_STRING_set(s, value.as_ptr(), value.len().try_into().unwrap()); + Ok(Self::from_ptr(s)) + } + } +} + +impl Asn1OctetStringRef { + /// Returns the octet string as an array of bytes. + #[corresponds(ASN1_STRING_get0_data)] + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr().cast()), self.len()) } + } + + /// Returns the number of bytes in the octet string. + #[corresponds(ASN1_STRING_length)] + pub fn len(&self) -> usize { + unsafe { ffi::ASN1_STRING_length(self.as_ptr().cast()) as usize } + } + + /// Determines if the string is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::ASN1_OBJECT; + fn drop = ffi::ASN1_OBJECT_free; + fn clone = ffi::OBJ_dup; + + /// Object Identifier + /// + /// Represents an ASN.1 Object. Typically, NIDs, or numeric identifiers + /// are stored as a table within the [`Nid`] module. These constants are + /// used to determine attributes of a certificate, such as mapping the + /// attribute "CommonName" to "CN" which is represented as the OID of 13. + /// This attribute is a constant in the [`nid::COMMONNAME`]. + /// + /// OpenSSL documentation at [`OBJ_nid2obj`] + /// + /// [`Nid`]: ../nid/index.html + /// [`nid::COMMONNAME`]: ../nid/constant.COMMONNAME.html + /// [`OBJ_nid2obj`]: https://www.openssl.org/docs/manmaster/crypto/OBJ_obj2nid.html + pub struct Asn1Object; + /// A reference to an [`Asn1Object`]. + pub struct Asn1ObjectRef; +} + +impl Stackable for Asn1Object { + type StackType = ffi::stack_st_ASN1_OBJECT; +} + +impl Asn1Object { + /// Constructs an ASN.1 Object Identifier from a string representation of the OID. + #[corresponds(OBJ_txt2obj)] + #[allow(clippy::should_implement_trait)] + pub fn from_str(txt: &str) -> Result<Asn1Object, ErrorStack> { + unsafe { + ffi::init(); + let txt = CString::new(txt).unwrap(); + let obj: *mut ffi::ASN1_OBJECT = cvt_p(ffi::OBJ_txt2obj(txt.as_ptr() as *const _, 0))?; + Ok(Asn1Object::from_ptr(obj)) + } + } + + /// Return the OID as an DER encoded array of bytes. This is the ASN.1 + /// value, not including tag or length. + /// + /// Requires OpenSSL 1.1.1 or newer. + #[corresponds(OBJ_get0_data)] + #[cfg(ossl111)] + pub fn as_slice(&self) -> &[u8] { + unsafe { + let len = ffi::OBJ_length(self.as_ptr()); + slice::from_raw_parts(ffi::OBJ_get0_data(self.as_ptr()), len) + } + } +} + +impl Asn1ObjectRef { + /// Returns the NID associated with this OID. + pub fn nid(&self) -> Nid { + unsafe { Nid::from_raw(ffi::OBJ_obj2nid(self.as_ptr())) } + } +} + +impl fmt::Display for Asn1ObjectRef { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + unsafe { + let mut buf = [0; 80]; + let len = ffi::OBJ_obj2txt( + buf.as_mut_ptr() as *mut _, + buf.len() as c_int, + self.as_ptr(), + 0, + ); + match str::from_utf8(&buf[..len as usize]) { + Err(_) => fmt.write_str("error"), + Ok(s) => fmt.write_str(s), + } + } + } +} + +impl fmt::Debug for Asn1ObjectRef { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str(self.to_string().as_str()) + } +} + +cfg_if! { + if #[cfg(any(ossl110, libressl273, boringssl))] { + use ffi::ASN1_STRING_get0_data; + } else { + #[allow(bad_style)] + unsafe fn ASN1_STRING_get0_data(s: *mut ffi::ASN1_STRING) -> *const ::libc::c_uchar { + ffi::ASN1_STRING_data(s) + } + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::ASN1_ENUMERATED; + fn drop = ffi::ASN1_ENUMERATED_free; + + /// An ASN.1 enumerated. + pub struct Asn1Enumerated; + /// A reference to an [`Asn1Enumerated`]. + pub struct Asn1EnumeratedRef; +} + +impl Asn1EnumeratedRef { + /// Get the value, if it fits in the required bounds. + #[corresponds(ASN1_ENUMERATED_get_int64)] + #[cfg(ossl110)] + pub fn get_i64(&self) -> Result<i64, ErrorStack> { + let mut crl_reason = 0; + unsafe { + cvt(ffi::ASN1_ENUMERATED_get_int64( + &mut crl_reason, + self.as_ptr(), + ))?; + } + Ok(crl_reason) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::bn::BigNum; + use crate::nid::Nid; + + /// Tests conversion between BigNum and Asn1Integer. + #[test] + fn bn_cvt() { + fn roundtrip(bn: BigNum) { + let large = Asn1Integer::from_bn(&bn).unwrap(); + assert_eq!(large.to_bn().unwrap(), bn); + } + + roundtrip(BigNum::from_dec_str("1000000000000000000000000000000000").unwrap()); + roundtrip(-BigNum::from_dec_str("1000000000000000000000000000000000").unwrap()); + roundtrip(BigNum::from_u32(1234).unwrap()); + roundtrip(-BigNum::from_u32(1234).unwrap()); + } + + #[test] + fn time_from_str() { + Asn1Time::from_str("99991231235959Z").unwrap(); + #[cfg(ossl111)] + Asn1Time::from_str_x509("99991231235959Z").unwrap(); + } + + #[test] + fn time_from_unix() { + let t = Asn1Time::from_unix(0).unwrap(); + assert_eq!("Jan 1 00:00:00 1970 GMT", t.to_string()); + } + + #[test] + #[cfg(ossl102)] + fn time_eq() { + let a = Asn1Time::from_str("99991231235959Z").unwrap(); + let b = Asn1Time::from_str("99991231235959Z").unwrap(); + let c = Asn1Time::from_str("99991231235958Z").unwrap(); + let a_ref = a.as_ref(); + let b_ref = b.as_ref(); + let c_ref = c.as_ref(); + assert!(a == b); + assert!(a != c); + assert!(a == b_ref); + assert!(a != c_ref); + assert!(b_ref == a); + assert!(c_ref != a); + assert!(a_ref == b_ref); + assert!(a_ref != c_ref); + } + + #[test] + #[cfg(ossl102)] + fn time_ord() { + let a = Asn1Time::from_str("99991231235959Z").unwrap(); + let b = Asn1Time::from_str("99991231235959Z").unwrap(); + let c = Asn1Time::from_str("99991231235958Z").unwrap(); + let a_ref = a.as_ref(); + let b_ref = b.as_ref(); + let c_ref = c.as_ref(); + assert!(a >= b); + assert!(a > c); + assert!(b <= a); + assert!(c < a); + + assert!(a_ref >= b); + assert!(a_ref > c); + assert!(b_ref <= a); + assert!(c_ref < a); + + assert!(a >= b_ref); + assert!(a > c_ref); + assert!(b <= a_ref); + assert!(c < a_ref); + + assert!(a_ref >= b_ref); + assert!(a_ref > c_ref); + assert!(b_ref <= a_ref); + assert!(c_ref < a_ref); + } + + #[test] + fn integer_to_owned() { + let a = Asn1Integer::from_bn(&BigNum::from_dec_str("42").unwrap()).unwrap(); + let b = a.to_owned().unwrap(); + assert_eq!( + a.to_bn().unwrap().to_dec_str().unwrap().to_string(), + b.to_bn().unwrap().to_dec_str().unwrap().to_string(), + ); + assert_ne!(a.as_ptr(), b.as_ptr()); + } + + #[test] + fn integer_cmp() { + let a = Asn1Integer::from_bn(&BigNum::from_dec_str("42").unwrap()).unwrap(); + let b = Asn1Integer::from_bn(&BigNum::from_dec_str("42").unwrap()).unwrap(); + let c = Asn1Integer::from_bn(&BigNum::from_dec_str("43").unwrap()).unwrap(); + assert!(a == b); + assert!(a != c); + assert!(a < c); + assert!(c > b); + } + + #[test] + fn object_from_str() { + let object = Asn1Object::from_str("2.16.840.1.101.3.4.2.1").unwrap(); + assert_eq!(object.nid(), Nid::SHA256); + } + + #[test] + fn object_from_str_with_invalid_input() { + Asn1Object::from_str("NOT AN OID") + .map(|object| object.to_string()) + .expect_err("parsing invalid OID should fail"); + } + + #[test] + #[cfg(ossl111)] + fn object_to_slice() { + let object = Asn1Object::from_str("2.16.840.1.101.3.4.2.1").unwrap(); + assert_eq!( + object.as_slice(), + &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01], + ); + } + + #[test] + fn asn1_octet_string() { + let octet_string = Asn1OctetString::new_from_bytes(b"hello world").unwrap(); + assert_eq!(octet_string.as_slice(), b"hello world"); + assert_eq!(octet_string.len(), 11); + } +} |