picky-asn1-0.7.2/.cargo_vcs_info.json0000644000000001500000000000100130150ustar { "git": { "sha1": "39e2d82c20ee66de0cf88f8949fe9fdfc30b6447" }, "path_in_vcs": "picky-asn1" }picky-asn1-0.7.2/CHANGELOG.md000064400000000000000000000052321046102023000134240ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased ## [0.7.2] 2023-03-27 ### Fixed - ASN.1 Integer minimal encoding ([#209](https://github.com/Devolutions/picky-rs/pull/209)) ## [0.7.1] 2022-11-07 ### Changed - Debug representation of `BitString`, `RestrictedString`, `OctetStringAsn1` and `IntegerAsn1` in hexadecimal ## [0.7.0] 2022-11-07 ### Added - `BitString::as_inner` method ## [0.6.0] 2022-08-01 ### Added - Implement `Zeroize` for `IntegerAsn1` (behind the `zeroize` feature) ### Changed - Bump minimal rustc version to 1.60 ## [0.5.0] 2022-02-02 ### Added - `Optional::is_default` - Support for `time 0.3` types conversions behind `time_conversion` feature gate - Implement `Default` for `Optional` when `T: Default` - Added `GeneralString` ### Changed - Bump minimal rustc version to 1.56 ## [0.4.0] 2021-08-09 ### Changed - Rename `Tag::number` into `Tag::inner` - Rename `ApplicationTag{0-15}` into `ExplicitContextTag{0-15}` - Rename `ContextTag{0-15}` into `ImplicitContextTag{0-15}` - Rename `Implicit` into `Optional` ### Added - `Tag::is_primitive` - `Tag::application_primitive` - `Tag::application_constructed` - `Tag::context_specific_primitive` - `Tag::context_specific_constructed` - `Tag::number` - `Tag::class` - `Tag::class_and_number` - `Tag::components` - `Tag::is_application` - `Tag::is_context_specific` - `Tag::is_universal` - `Tag::is_private` - `Tag::is_constructed` - `Tag::is_primitive` - `Tag::encoding` - `TagClass` - `Encoding` ### Removed - `Tag::APP_{0-15}` - `Tag::CTX_{0-15}` - `Tag::application` - `Tag::context_specific` ## [0.3.3] 2021-07-02 ### Fixed - Support for rustc 1.43 (accidently bumped in 0.3.2). See [#89](https://github.com/Devolutions/picky-rs/issues/89). ## [0.3.2] 2021-05-27 ### Added - Support for `BMPString` - Implement `Default` for `IA5StringAsn1`, `Asn1SetOf`, `Utf8String`, `IA5String` ## [0.3.1] 2021-01-11 ### Fixed - Fix bad `use`s statements to `serde::export` ## [0.3.0] 2020-08-31 ### Changed - Rename `IntegerAsn1`'s `from_unsigned_bytes_be` to `from_bytes_be_unsigned` - Rename `IntegerAsn1`'s `from_signed_bytes_be` to `from_bytes_be_signed` ## [0.2.2] 2020-07-07 ### Changed - Dependencies clean up ## [0.2.1] 2020-01-13 ### Fixed - Check for index out of bound in `IntegerAsn1::from_unsigned_bytes_be` ## [0.2.0] 2020-01-10 ### Added - Add `IntegerAsn1::from_unsigned_bytes_be` ### Changed - Rename `IntegerAsn1::as_bytes_be` to `IntegerAsn1::as_unsigned_bytes_be`. picky-asn1-0.7.2/Cargo.toml0000644000000026600000000000100110230ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.60" name = "picky-asn1" version = "0.7.2" authors = [ "Benoît CORTIER ", "Alexandr Yusuk ", "Brian Maher", ] description = "Provide ASN.1 simple types" readme = "README.md" keywords = [ "serde", "asn1", "serialize", "deserialize", ] categories = ["encoding"] license = "MIT OR Apache-2.0" repository = "https://github.com/Devolutions/picky-rs" [dependencies.chrono] version = "0.4.23" optional = true [dependencies.oid] version = "0.2.1" features = ["serde_support"] default-features = false [dependencies.serde] version = "1.0.152" features = ["derive"] default-features = false [dependencies.serde_bytes] version = "0.11.9" [dependencies.time] version = "0.3.19" optional = true [dependencies.zeroize] version = "^1.5" optional = true [dev-dependencies] [features] chrono_conversion = ["chrono"] time_conversion = ["time"] zeroize = ["dep:zeroize"] picky-asn1-0.7.2/Cargo.toml.orig000064400000000000000000000017071046102023000145050ustar 00000000000000[package] name = "picky-asn1" version = "0.7.2" edition = "2021" rust-version = "1.60" authors = [ "Benoît CORTIER ", "Alexandr Yusuk ", "Brian Maher", ] keywords = ["serde", "asn1", "serialize", "deserialize"] categories = ["encoding"] description = "Provide ASN.1 simple types" license = "MIT OR Apache-2.0" repository = "https://github.com/Devolutions/picky-rs" readme = "README.md" [dependencies] serde = { version = "1.0.152", default-features = false, features = ["derive"] } oid = { version = "0.2.1", default-features = false, features = ["serde_support"] } serde_bytes = "0.11.9" chrono = { version = "0.4.23", optional = true } time = { version = "0.3.19", optional = true } zeroize = { version = "^1.5", optional = true } [dev-dependencies] picky-asn1-der = { path = "../picky-asn1-der" } [features] chrono_conversion = ["chrono"] time_conversion = ["time"] zeroize = ["dep:zeroize"] picky-asn1-0.7.2/README.md000064400000000000000000000012551046102023000130730ustar 00000000000000[![Crates.io](https://img.shields.io/crates/v/picky-asn1.svg)](https://crates.io/crates/picky-asn1) [![docs.rs](https://docs.rs/picky-asn1/badge.svg)](https://docs.rs/picky-asn1) ![Crates.io](https://img.shields.io/crates/l/picky-asn1) Compatible with rustc 1.60. Minimal rustc version bumps happen [only with minor number bumps in this project](https://github.com/Devolutions/picky-rs/issues/89#issuecomment-868303478). # picky-asn1 Defines wrappers for ASN.1 types: - Integer (as big integer) - Bit String - Object Identifier - Utf8 String - Numeric String - Printable String - IA5 String - Generalized Time - UTC Time - Application Tags from 0 to 15 - Context Tags from 0 to 15 picky-asn1-0.7.2/src/bit_string.rs000064400000000000000000000222501046102023000151130ustar 00000000000000use core::fmt; use serde::{de, ser}; /// A bit string. /// /// Rewrite based on [this implementation](https://melvinw.github.io/rust-asn1/asn1/struct.BitString.html) by Melvin Walls Jr. /// licensed with /// /// > The MIT License (MIT) /// > /// > Copyright (c) 2016 Melvin Walls Jr. /// > /// > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: /// > /// > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. /// > /// > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /// /// # Examples /// /// ``` /// use picky_asn1::bit_string::BitString; /// /// let mut b = BitString::with_len(60); /// /// b.set(0, true); /// assert_eq!(b.is_set(0), true); /// /// b.set(59, true); /// assert_eq!(b.is_set(59), true); /// /// // because len is 60, attempts at setting anything greater than 59 won't change anything /// b.set(63, true); /// assert_eq!(b.is_set(63), false); /// ``` #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] pub struct BitString { data: Vec, } impl BitString { /// Returns inner bytes slice pub fn as_bytes(&self) -> &[u8] { &self.data } fn h_number_of_unused_bits(data_size: usize, num_bits: usize) -> u8 { (data_size * 8 - num_bits) as u8 } /// Construct a `BitString` of length `n` with all bits set to 0. pub fn with_len(num_bits: usize) -> BitString { let data_size = num_bits / 8 + if num_bits % 8 == 0 { 0 } else { 1 }; let mut data = vec![0x00u8; data_size + 1]; data[0] = Self::h_number_of_unused_bits(data_size, num_bits); BitString { data } } /// Construct a `BitString` of length `n` with initial values contained in `data`. /// /// # Examples /// /// ``` /// use picky_asn1::bit_string::BitString; /// /// let v: Vec = vec![0x00, 0x02]; /// let b = BitString::with_bytes_and_len(v, 15); /// assert_eq!(b.is_set(0), false); /// assert_eq!(b.is_set(14), true); /// /// // because len is 15, everything greater than 14 will returns false /// assert_eq!(b.is_set(15), false); /// assert_eq!(b.is_set(938), false); /// ``` pub fn with_bytes_and_len(data: V, num_bits: usize) -> BitString where V: Into>, { let mut data = data.into(); let number_of_unused = Self::h_number_of_unused_bits(data.len(), num_bits); data.insert(0, number_of_unused); BitString { data } } /// Construct a `BitString` from initial values contained in `data`. /// Length is inferred fromthe size of `data`. /// /// # Examples /// /// ``` /// use picky_asn1::bit_string::BitString; /// /// let v: Vec = vec![0x00, 0x02]; /// let b = BitString::with_bytes(v); /// assert_eq!(b.is_set(0), false); /// assert_eq!(b.is_set(14), true); /// ``` pub fn with_bytes(data: V) -> BitString where V: Into>, { let mut data = data.into(); data.insert(0, 0); // no unused bits BitString { data } } /// Get the number of available bits in the `BitString` pub fn get_num_bits(&self) -> usize { (self.data.len() - 1) * 8 - self.data[0] as usize } /// Set the length of a `BitString` with each additional slot filled with 0. /// /// # Examples /// /// ``` /// use picky_asn1::bit_string::BitString; /// /// let v: Vec = vec![0x01, 0x01]; /// let mut b = BitString::with_bytes_and_len(v, 16); /// assert_eq!(b.is_set(7), true); /// assert_eq!(b.is_set(15), true); /// /// b.set_num_bits(8); /// assert_eq!(b.is_set(7), true); /// b.set(15, true); // attempts to set a value out of the bounds are ignored /// assert_eq!(b.is_set(15), false); /// /// b.set_num_bits(16); /// assert_eq!(b.is_set(7), true); /// assert_eq!(b.is_set(15), false); /// b.set(15, true); /// assert_eq!(b.is_set(15), true); /// ``` pub fn set_num_bits(&mut self, num_bits: usize) { let new_size = num_bits / 8 + if num_bits % 8 == 0 { 0 } else { 1 }; self.data[0] = Self::h_number_of_unused_bits(new_size, num_bits); self.data.resize(new_size + 1, 0); } /// Check if bit `i` is set. /// /// # Examples /// /// ``` /// use picky_asn1::bit_string::BitString; /// /// let mut b = BitString::with_len(10); /// assert_eq!(b.is_set(7), false); /// b.set(7, true); /// assert_eq!(b.is_set(7), true); /// ``` pub fn is_set(&self, i: usize) -> bool { if i > self.get_num_bits() { return false; } let bucket = i / 8; let pos = i - bucket * 8; let mask = (1 << (7 - pos)) as u8; self.data[bucket + 1] & mask != 0 } /// Set bit `i` to `val`. pub fn set(&mut self, i: usize, val: bool) { if i > self.get_num_bits() { return; } let bucket = i / 8; let pos = i - bucket * 8; let mask = (1 << (7 - pos)) as u8; if val { self.data[bucket + 1] |= mask; } else { self.data[bucket + 1] &= !mask; } } pub fn get_num_unused_bits(&self) -> u8 { self.data[0] } pub fn get_num_buckets(&self) -> usize { self.data.len() - 1 } pub fn get_bucket(&self, i: usize) -> u8 { self.data[i + 1] } pub fn get_bucket_mut(&mut self, i: usize) -> &mut u8 { &mut self.data[i + 1] } pub fn set_bucket(&mut self, i: usize, value: u8) { self.data[i + 1] = value } /// Returns an immutabe view on the payload. /// /// # Examples /// /// ``` /// use picky_asn1::bit_string::BitString; /// /// let v: Vec = vec![0x01, 0x00]; /// let mut b = BitString::with_bytes_and_len(v, 15); /// b.set(14, true); /// let payload = b.payload_view(); /// assert_eq!(payload, &[0x01, 0x02]); /// ``` pub fn payload_view(&self) -> &[u8] { &self.data[1..] } /// Returns a mutabe view on the payload. /// /// # Examples /// /// ``` /// use picky_asn1::bit_string::BitString; /// /// let v: Vec = vec![0x01, 0x00]; /// let mut b = BitString::with_bytes_and_len(v, 15); /// b.set(14, true); /// let payload = b.payload_view_mut(); /// payload[0] = 0x20; /// assert_eq!(payload, &[0x20, 0x02]); /// ``` pub fn payload_view_mut(&mut self) -> &mut [u8] { &mut self.data[1..] } } impl fmt::Debug for BitString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "0x")?; self.data.iter().try_for_each(|byte| write!(f, "{byte:02X}"))?; Ok(()) } } impl From for Vec { /// Strips 'unused bits count' byte and returns payload. /// /// # Examples /// /// ``` /// use picky_asn1::bit_string::BitString; /// /// let v: Vec = vec![0x01, 0x00]; /// let mut b = BitString::with_bytes_and_len(v, 15); /// b.set(14, true); /// let payload: Vec = b.into(); /// assert_eq!(payload, vec![0x01, 0x02]); /// ``` fn from(mut bs: BitString) -> Self { bs.data.drain(1..).collect() } } impl<'de> de::Deserialize<'de> for BitString { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = BitString; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid buffer representing a bit string") } fn visit_bytes(self, v: &[u8]) -> Result where E: de::Error, { self.visit_byte_buf(v.to_vec()) } fn visit_byte_buf(self, v: Vec) -> Result where E: de::Error, { Ok(BitString { data: v }) } } deserializer.deserialize_byte_buf(Visitor) } } impl ser::Serialize for BitString { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: ser::Serializer, { serializer.serialize_bytes(&self.data) } } impl Default for BitString { fn default() -> Self { BitString::with_len(0) } } picky-asn1-0.7.2/src/date.rs000064400000000000000000000273601046102023000136730ustar 00000000000000use serde::{de, ser, Deserializer, Serializer}; use std::fmt; pub trait TimeRepr where Self: Sized, { fn serialize( date: &Date, serializer: S, ) -> Result<::Ok, ::Error> where S: ser::Serializer; fn deserialize<'de, D>(deserializer: D) -> Result, >::Error> where D: de::Deserializer<'de>; } /// A basic Date struct. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Date { year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8, _pd: std::marker::PhantomData, } impl Date { /// Create a new Date without validation. /// /// # Safety /// /// You have to make sure you're not building an invalid date. pub unsafe fn new_unchecked(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> Date { Self { year, month, day, hour, minute, second, _pd: std::marker::PhantomData, } } pub fn new(year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> Option> { if (1..=12).contains(&month) && (1..=32).contains(&day) && hour < 24 && minute < 60 && second < 60 { Some(Self { year, month, day, hour, minute, second, _pd: std::marker::PhantomData, }) } else { None } } pub fn year(&self) -> u16 { self.year } pub fn month(&self) -> u8 { self.month } pub fn day(&self) -> u8 { self.day } pub fn hour(&self) -> u8 { self.hour } pub fn minute(&self) -> u8 { self.minute } pub fn second(&self) -> u8 { self.second } } impl ser::Serialize for Date { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: ser::Serializer, { TR::serialize(self, serializer) } } impl<'de, TR: TimeRepr> de::Deserialize<'de> for Date { fn deserialize(deserializer: D) -> Result>::Error> where D: de::Deserializer<'de>, { TR::deserialize(deserializer) } } trait DateDigitReader { fn read_digit(&self, idx: usize) -> u8; #[inline] fn read_and_merge_with_next(&self, idx: usize) -> u8 { self.read_digit(idx) * 10 + self.read_digit(idx + 1) } } impl DateDigitReader for [u8] { #[inline] fn read_digit(&self, idx: usize) -> u8 { self[idx] & 0x0F } } #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct UTCTimeRepr; pub type UTCTime = Date; impl TimeRepr for UTCTimeRepr { fn serialize(date: &Date, serializer: S) -> Result<::Ok, ::Error> where S: ser::Serializer, { let mut encoded = [ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, ]; let year = if date.year() >= 2000 { date.year() - 2000 } else { date.year() - 1900 }; encoded[0] |= (year / 10) as u8; encoded[1] |= (year % 10) as u8; encoded[2] |= date.month() / 10; encoded[3] |= date.month() % 10; encoded[4] |= date.day() / 10; encoded[5] |= date.day() % 10; encoded[6] |= date.hour() / 10; encoded[7] |= date.hour() % 10; encoded[8] |= date.minute() / 10; encoded[9] |= date.minute() % 10; encoded[10] |= date.second() / 10; encoded[11] |= date.second() % 10; serializer.serialize_bytes(&encoded) } fn deserialize<'de, D>(deserializer: D) -> Result, >::Error> where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = Date; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid buffer representing an Asn1 UTCTime") } fn visit_bytes(self, v: &[u8]) -> Result where E: de::Error, { if v.len() != 13 { return Err(E::invalid_value( de::Unexpected::Other("unsupported date format"), &"a valid buffer representing an Asn1 UTCTime (exactly 13 bytes required)", )); } let yyyy = { let yy = v.read_and_merge_with_next(0) as u16; if yy >= 50 { 1900 + yy } else { 2000 + yy } }; let month = v.read_and_merge_with_next(2); let day = v.read_and_merge_with_next(4); let hour = v.read_and_merge_with_next(6); let minute = v.read_and_merge_with_next(8); let second = v.read_and_merge_with_next(10); let dt = Date::new(yyyy, month, day, hour, minute, second).ok_or_else(|| { E::invalid_value( de::Unexpected::Other("invalid parameters provided to Date constructor"), &"valid parameters for Date", ) })?; Ok(dt) } } deserializer.deserialize_bytes(Visitor) } } #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct GeneralizedTimeRepr; pub type GeneralizedTime = Date; impl TimeRepr for GeneralizedTimeRepr { fn serialize( date: &Date, serializer: S, ) -> Result<::Ok, ::Error> where S: ser::Serializer, { let mut encoded = [ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, ]; encoded[0] |= (date.year() / 1000) as u8; encoded[1] |= ((date.year() % 1000) / 100) as u8; encoded[2] |= ((date.year() % 100) / 10) as u8; encoded[3] |= (date.year() % 10) as u8; encoded[4] |= date.month() / 10; encoded[5] |= date.month() % 10; encoded[6] |= date.day() / 10; encoded[7] |= date.day() % 10; encoded[8] |= date.hour() / 10; encoded[9] |= date.hour() % 10; encoded[10] |= date.minute() / 10; encoded[11] |= date.minute() % 10; encoded[12] |= date.second() / 10; encoded[13] |= date.second() % 10; serializer.serialize_bytes(&encoded) } fn deserialize<'de, D>(deserializer: D) -> Result, >::Error> where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = Date; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid buffer representing an Asn1 GeneralizedTime") } fn visit_bytes(self, v: &[u8]) -> Result where E: de::Error, { if v.len() != 15 { return Err(E::invalid_value( de::Unexpected::Other("unsupported date format"), &"a valid buffer representing an Asn1 GeneralizedTime (exactly 15 bytes required)", )); } let yyyy = v.read_and_merge_with_next(0) as u16 * 100 + v.read_and_merge_with_next(2) as u16; let month = v.read_and_merge_with_next(4); let day = v.read_and_merge_with_next(6); let hour = v.read_and_merge_with_next(8); let minute = v.read_and_merge_with_next(10); let second = v.read_and_merge_with_next(12); let dt = Date::new(yyyy, month, day, hour, minute, second).ok_or_else(|| { E::invalid_value( de::Unexpected::Other("invalid parameters provided to Date constructor"), &"valid parameters for Date", ) })?; Ok(dt) } } deserializer.deserialize_bytes(Visitor) } } #[cfg(feature = "time_conversion")] mod time_convert { use super::*; use time::{OffsetDateTime, PrimitiveDateTime}; impl From for Date { fn from(d: PrimitiveDateTime) -> Self { Self::from(d.assume_utc()) } } impl TryFrom> for PrimitiveDateTime { type Error = time::error::ComponentRange; fn try_from(d: Date) -> Result { let date = time::Date::from_calendar_date(i32::from(d.year), time::Month::try_from(d.month)?, d.day)?; let time = time::Time::from_hms(d.hour, d.minute, d.second)?; Ok(Self::new(date, time)) } } impl From for Date { fn from(d: OffsetDateTime) -> Self { Self { year: u16::try_from(d.year()).unwrap(), month: u8::from(d.month()), day: d.day(), hour: d.hour(), minute: d.minute(), second: d.second(), _pd: std::marker::PhantomData, } } } impl TryFrom> for OffsetDateTime { type Error = time::error::ComponentRange; fn try_from(d: Date) -> Result { Ok(PrimitiveDateTime::try_from(d)?.assume_utc()) } } } #[cfg(feature = "chrono_conversion")] mod chrono_convert { use super::*; use chrono::naive::NaiveDateTime; use chrono::{DateTime, Datelike, NaiveDate, Timelike, Utc}; impl From for Date { fn from(d: NaiveDateTime) -> Self { Self { year: u16::try_from(d.year()).unwrap(), month: u8::try_from(d.month()).unwrap(), day: u8::try_from(d.day()).unwrap(), hour: u8::try_from(d.hour()).unwrap(), minute: u8::try_from(d.minute()).unwrap(), second: u8::try_from(d.second()).unwrap(), _pd: std::marker::PhantomData, } } } impl From> for NaiveDateTime { fn from(date: Date) -> Self { NaiveDate::from_ymd_opt(i32::from(date.year), u32::from(date.month), u32::from(date.day)) .unwrap() .and_hms_opt(u32::from(date.hour), u32::from(date.minute), u32::from(date.second)) .unwrap() } } impl From> for Date { fn from(d: DateTime) -> Self { Self { year: u16::try_from(d.year()).unwrap(), month: u8::try_from(d.month()).unwrap(), day: u8::try_from(d.day()).unwrap(), hour: u8::try_from(d.hour()).unwrap(), minute: u8::try_from(d.minute()).unwrap(), second: u8::try_from(d.second()).unwrap(), _pd: std::marker::PhantomData, } } } impl From> for DateTime { fn from(date: Date) -> Self { DateTime::::from_utc(date.into(), Utc) } } } picky-asn1-0.7.2/src/lib.rs000064400000000000000000000017331046102023000135200ustar 00000000000000pub mod bit_string; pub mod date; pub mod restricted_string; pub mod tag; pub mod wrapper; use tag::Tag; pub trait Asn1Type { const TAG: Tag; const NAME: &'static str; } impl Asn1Type for () { const TAG: Tag = Tag::NULL; const NAME: &'static str = "()"; } impl Asn1Type for String { const TAG: Tag = Tag::UTF8_STRING; const NAME: &'static str = "String"; } impl Asn1Type for bool { const TAG: Tag = Tag::BOOLEAN; const NAME: &'static str = "bool"; } impl Asn1Type for u8 { const TAG: Tag = Tag::INTEGER; const NAME: &'static str = "u8"; } impl Asn1Type for u16 { const TAG: Tag = Tag::INTEGER; const NAME: &'static str = "u16"; } impl Asn1Type for u32 { const TAG: Tag = Tag::INTEGER; const NAME: &'static str = "u32"; } impl Asn1Type for u64 { const TAG: Tag = Tag::INTEGER; const NAME: &'static str = "u64"; } impl Asn1Type for u128 { const TAG: Tag = Tag::INTEGER; const NAME: &'static str = "u128"; } picky-asn1-0.7.2/src/restricted_string.rs000064400000000000000000000210421046102023000165030ustar 00000000000000use serde::{de, ser}; use std::error::Error; use std::fmt; use std::marker::PhantomData; use std::ops::Deref; use std::str::FromStr; // === CharSetError === // #[derive(Debug)] pub struct CharSetError; impl Error for CharSetError {} impl fmt::Display for CharSetError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { writeln!(f, "invalid charset") } } // === CharSet === // pub trait CharSet { const NAME: &'static str; /// Checks whether a sequence is a valid string or not. fn check(data: &[u8]) -> bool; } // === RestrictedString === // /// A generic restricted character string. #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct RestrictedString { data: Vec, marker: PhantomData, } impl RestrictedString { /// Create a new RestrictedString without CharSet validation. /// /// # Safety /// /// You have to make sure the right CharSet is used. pub unsafe fn new_unchecked(data: V) -> Self where V: Into>, { RestrictedString { data: data.into(), marker: PhantomData, } } pub fn new(data: V) -> Result where V: Into>, { let data = data.into(); if !C::check(&data) { return Err(CharSetError); }; Ok(RestrictedString { data, marker: PhantomData, }) } pub fn from_string(s: String) -> Result { Self::new(s.into_bytes()) } /// Converts into underlying bytes. pub fn into_bytes(self) -> Vec { self.data } /// Returns underlying bytes. pub fn as_bytes(&self) -> &[u8] { &self.data } } impl Deref for RestrictedString { type Target = [u8]; fn deref(&self) -> &Self::Target { &self.data } } impl FromStr for RestrictedString { type Err = CharSetError; fn from_str(s: &str) -> Result { Self::new(s.as_bytes()) } } impl AsRef<[u8]> for RestrictedString { fn as_ref(&self) -> &[u8] { &self.data } } impl fmt::Debug for RestrictedString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}(", C::NAME)?; if let Ok(utf8) = std::str::from_utf8(&self.data) { fmt::Debug::fmt(utf8, f)?; } else { write!(f, "0x")?; self.data.iter().try_for_each(|byte| write!(f, "{byte:02X}"))?; } write!(f, ")")?; Ok(()) } } impl fmt::Display for RestrictedString { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&String::from_utf8_lossy(&self.data), fmt) } } impl From> for Vec { fn from(rs: RestrictedString) -> Self { rs.into_bytes() } } impl<'de, C> de::Deserialize<'de> for RestrictedString where C: CharSet, { fn deserialize(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, { struct Visitor(std::marker::PhantomData); impl<'de, C> de::Visitor<'de> for Visitor where C: CharSet, { type Value = RestrictedString; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid buffer representing a restricted string") } fn visit_bytes(self, v: &[u8]) -> Result where E: de::Error, { self.visit_byte_buf(v.to_vec()) } fn visit_byte_buf(self, v: Vec) -> Result where E: de::Error, { RestrictedString::new(v).map_err(|_| { E::invalid_value( de::Unexpected::Other("invalid charset"), &"a buffer representing a string using the right charset", ) }) } } deserializer.deserialize_byte_buf(Visitor(std::marker::PhantomData)) } } impl ser::Serialize for RestrictedString { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: ser::Serializer, { serializer.serialize_bytes(&self.data) } } // === NumericString === // /// 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, and SPACE #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct NumericCharSet; pub type NumericString = RestrictedString; impl CharSet for NumericCharSet { const NAME: &'static str = "NUMERIC"; fn check(data: &[u8]) -> bool { for &c in data { if c != b' ' && !c.is_ascii_digit() { return false; } } true } } // === PrintableString === // /// a-z, A-Z, ' () +,-.?:/= and SPACE #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct PrintableCharSet; pub type PrintableString = RestrictedString; impl CharSet for PrintableCharSet { const NAME: &'static str = "PRINTABLE"; fn check(data: &[u8]) -> bool { for &c in data { if !(c.is_ascii_alphanumeric() || c == b' ' || c == b'\'' || c == b'(' || c == b')' || c == b'+' || c == b',' || c == b'-' || c == b'.' || c == b'/' || c == b':' || c == b'=' || c == b'?') { return false; } } true } } // === Utf8String === // /// any character from a recognized alphabet (including ASCII control characters) #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct Utf8CharSet; pub type Utf8String = RestrictedString; impl CharSet for Utf8CharSet { const NAME: &'static str = "UTF8"; fn check(data: &[u8]) -> bool { std::str::from_utf8(data).is_ok() } } // === IA5String === // /// First 128 ASCII characters (values from `0x00` to `0x7F`) /// Used to represent ISO 646 (IA5) characters. #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct IA5CharSet; pub type IA5String = RestrictedString; impl CharSet for IA5CharSet { const NAME: &'static str = "IA5"; fn check(data: &[u8]) -> bool { for &c in data { if !c.is_ascii() { return false; } } true } } #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct BMPCharSet; pub type BMPString = RestrictedString; impl CharSet for BMPCharSet { const NAME: &'static str = "BMP"; fn check(data: &[u8]) -> bool { if data.len() % 2 != 0 { return false; } let u16_it = data .chunks_exact(2) .into_iter() .map(|elem| u16::from_be_bytes([elem[1], elem[0]])); core::char::decode_utf16(u16_it).all(|c| matches!(c, Ok(_))) } } #[cfg(test)] mod tests { use super::*; #[test] fn valid_printable_string() { PrintableString::from_str("29INRUSAET3snre?:=tanui83 9283019").expect("valid string"); } #[test] fn invalid_printable_string() { assert!(PrintableString::from_str("1224na÷日本語はむずかちー−×—«BUeisuteurnt").is_err()); } #[test] fn valid_numeric_string() { NumericString::from_str("2983 9283019").expect("valid string"); } #[test] fn invalid_numeric_string() { assert!(NumericString::from_str("1224na÷日本語はむずかちー−×—«BUeisuteurnt").is_err()); } #[test] fn valid_ia5_string() { IA5String::from_str("BUeisuteurnt").expect("valid string"); } #[test] fn invalid_ia5_string() { assert!(IA5String::from_str("BUéisuteurnt").is_err()); } #[test] fn valid_utf8_string() { Utf8String::from_str("1224na÷日本語はむずかちー−×—«BUeisuteurnt").expect("valid string"); } #[test] fn valid_bmp_string() { BMPString::from_str("语言处理").expect("valid unicode string"); } #[test] fn invalid_bmp_string() { assert!(BMPString::from_str("1224na÷日本語はむずかちー−×—«BUeisuteurnt").is_err()) } } picky-asn1-0.7.2/src/tag.rs000064400000000000000000000210711046102023000135220ustar 00000000000000use serde::de; use std::fmt; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Encoding { Primitive, Constructed, } impl fmt::Display for Encoding { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Primitive => write!(f, "PRIMITIVE"), Self::Constructed => write!(f, "CONSTRUCTED"), } } } #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum TagClass { Universal, Application, ContextSpecific, Private, } impl fmt::Display for TagClass { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Universal => write!(f, "UNIVERSAL"), Self::Application => write!(f, "APPLICATION"), Self::ContextSpecific => write!(f, "CONTEXT_SPECIFIC"), Self::Private => write!(f, "PRIVATE"), } } } #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Tag(u8); impl Tag { pub const BOOLEAN: Self = Tag(0x01); pub const INTEGER: Self = Tag(0x02); pub const BIT_STRING: Self = Tag(0x03); pub const OCTET_STRING: Self = Tag(0x04); pub const NULL: Self = Tag(0x05); pub const OID: Self = Tag(0x06); pub const REAL: Self = Tag(0x09); pub const UTF8_STRING: Self = Tag(0x0C); pub const RELATIVE_OID: Self = Tag(0xD); pub const NUMERIC_STRING: Self = Tag(0x12); pub const PRINTABLE_STRING: Self = Tag(0x13); pub const TELETEX_STRING: Self = Tag(0x14); pub const VIDEOTEX_STRING: Self = Tag(0x15); pub const IA5_STRING: Self = Tag(0x16); pub const BMP_STRING: Self = Tag(0x1E); pub const UTC_TIME: Self = Tag(0x17); pub const GENERALIZED_TIME: Self = Tag(0x18); pub const SEQUENCE: Self = Tag(0x30); pub const SET: Self = Tag(0x31); pub const GENERAL_STRING: Self = Tag(0x1b); #[inline] pub const fn application_primitive(number: u8) -> Self { Tag(number & 0x1F | 0x40) } #[inline] pub const fn application_constructed(number: u8) -> Self { Tag(number & 0x1F | 0x60) } #[inline] pub const fn context_specific_primitive(number: u8) -> Self { Tag(number & 0x1F | 0x80) } #[inline] pub const fn context_specific_constructed(number: u8) -> Self { Tag(number & 0x1F | 0xA0) } /// Identifier octets as u8 #[inline] pub const fn inner(self) -> u8 { self.0 } /// Tag number of the ASN.1 value (filtering class bits and constructed bit with a mask) #[inline] pub const fn number(self) -> u8 { self.0 & 0x1F } // TODO: need version bump to be made const pub fn class(self) -> TagClass { match self.0 & 0xC0 { 0x00 => TagClass::Universal, 0x40 => TagClass::Application, 0x80 => TagClass::ContextSpecific, _ /* 0xC0 */ => TagClass::Private, } } // TODO: need version bump to be made const pub fn class_and_number(self) -> (TagClass, u8) { (self.class(), self.number()) } // TODO: need version bump to be made const pub fn components(self) -> (TagClass, Encoding, u8) { (self.class(), self.encoding(), self.number()) } #[inline] pub const fn is_application(self) -> bool { self.0 & 0xC0 == 0x40 } #[inline] pub const fn is_context_specific(self) -> bool { self.0 & 0xC0 == 0x80 } #[inline] pub const fn is_universal(self) -> bool { self.0 & 0xC0 == 0x00 } #[inline] pub const fn is_private(self) -> bool { self.0 & 0xC0 == 0xC0 } #[inline] pub const fn is_constructed(self) -> bool { self.0 & 0x20 == 0x20 } #[inline] pub const fn is_primitive(self) -> bool { !self.is_constructed() } // TODO: need version bump to be made const #[inline] pub fn encoding(self) -> Encoding { if self.is_constructed() { Encoding::Constructed } else { Encoding::Primitive } } } impl From for Tag { fn from(tag: u8) -> Self { Self(tag) } } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Tag::BOOLEAN => write!(f, "BOOLEAN"), Tag::INTEGER => write!(f, "INTEGER"), Tag::BIT_STRING => write!(f, "BIT STRING"), Tag::OCTET_STRING => write!(f, "OCTET STRING"), Tag::NULL => write!(f, "NULL"), Tag::OID => write!(f, "OBJECT IDENTIFIER"), Tag::REAL => write!(f, "REAL"), Tag::UTF8_STRING => write!(f, "UTF8String"), Tag::RELATIVE_OID => write!(f, "RELATIVE-OID"), Tag::NUMERIC_STRING => write!(f, "NumericString"), Tag::PRINTABLE_STRING => write!(f, "PrintableString"), Tag::TELETEX_STRING => write!(f, "TeletexString"), Tag::VIDEOTEX_STRING => write!(f, "VideotexString"), Tag::IA5_STRING => write!(f, "IA5String"), Tag::BMP_STRING => write!(f, "BMPString"), Tag::UTC_TIME => write!(f, "UTCTime"), Tag::GENERALIZED_TIME => write!(f, "GeneralizedTime"), Tag::SEQUENCE => write!(f, "SEQUENCE"), Tag::SET => write!(f, "SET"), Tag::GENERAL_STRING => write!(f, "GeneralString"), other => write!(f, "{}({:02X}) {}", other.class(), other.number(), other.encoding()), } } } impl fmt::Debug for Tag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Tag({}[{:02X}])", self, self.0) } } /// Used to peek next tag by using `Deserializer::deserialize_identifier`. /// /// Can be used to implement ASN.1 Choice. /// /// # Examples /// ``` /// use serde::de; /// use picky_asn1::{ /// wrapper::{IntegerAsn1, Utf8StringAsn1}, /// tag::{Tag, TagPeeker}, /// }; /// use std::fmt; /// /// pub enum MyChoice { /// Integer(u32), /// Utf8String(String), /// } /// /// impl<'de> de::Deserialize<'de> for MyChoice { /// fn deserialize(deserializer: D) -> Result>::Error> /// where /// D: de::Deserializer<'de>, /// { /// struct Visitor; /// /// impl<'de> de::Visitor<'de> for Visitor { /// type Value = MyChoice; /// /// fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { /// formatter.write_str("a valid MyChoice") /// } /// /// fn visit_seq(self, mut seq: A) -> Result /// where /// A: de::SeqAccess<'de>, /// { /// match seq.next_element::()?.unwrap().next_tag { /// Tag::INTEGER => { /// let value = seq.next_element::()?.unwrap(); /// Ok(MyChoice::Integer(value)) /// } /// Tag::UTF8_STRING => { /// let value = seq.next_element::()?.unwrap(); /// Ok(MyChoice::Utf8String(value)) /// } /// _ => Err(de::Error::invalid_value( /// de::Unexpected::Other( /// "[MyChoice] unsupported or unknown choice value", /// ), /// &"a supported choice value", /// )) /// } /// } /// } /// /// deserializer.deserialize_enum("MyChoice", &["Integer", "Utf8String"], Visitor) /// } /// } /// /// let buffer = b"\x0C\x06\xE8\x8B\x97\xE5\xAD\x97"; /// let my_choice: MyChoice = picky_asn1_der::from_bytes(buffer).unwrap(); /// match my_choice { /// MyChoice::Integer(_) => panic!("wrong variant"), /// MyChoice::Utf8String(string) => assert_eq!(string, "苗字"), /// } /// ``` #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TagPeeker { pub next_tag: Tag, } impl<'de> de::Deserialize<'de> for TagPeeker { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = TagPeeker; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid ASN.1 tag") } fn visit_u8(self, v: u8) -> Result where E: de::Error, { Ok(TagPeeker { next_tag: v.into() }) } } deserializer.deserialize_identifier(Visitor) } } picky-asn1-0.7.2/src/wrapper.rs000064400000000000000000000611541046102023000144350ustar 00000000000000use crate::bit_string::BitString; use crate::date::{GeneralizedTime, UTCTime}; use crate::restricted_string::{BMPString, IA5String, NumericString, PrintableString, Utf8String}; use crate::tag::Tag; use crate::Asn1Type; use oid::ObjectIdentifier; use serde::{de, ser, Deserialize, Serialize}; use std::fmt; use std::ops::{Deref, DerefMut}; /// Generate a thin ASN1 wrapper type with associated tag /// and name for serialization and deserialization purpose. macro_rules! asn1_wrapper { (struct $wrapper_ty:ident ( $wrapped_ty:ident ), $tag:expr) => { /// Wrapper type #[derive(Debug, PartialEq, Clone)] pub struct $wrapper_ty(pub $wrapped_ty); impls! { $wrapper_ty ( $wrapped_ty ), $tag } }; (auto struct $wrapper_ty:ident ( $wrapped_ty:ident ), $tag:expr) => { /// Wrapper type #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct $wrapper_ty(pub $wrapped_ty); impls! { $wrapper_ty ( $wrapped_ty ), $tag } }; (special tag struct $wrapper_ty:ident < $generic:ident >, $tag:expr) => { /// Wrapper type for special tag #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct $wrapper_ty<$generic>(pub $generic); impls! { $wrapper_ty < $generic >, $tag } }; (auto collection struct $wrapper_ty:ident < T >, $tag:expr) => { /// Asn1 wrapper around a collection of elements of the same type. #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct $wrapper_ty( #[serde( serialize_with = "serialize_vec", deserialize_with = "deserialize_vec", bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>") )] pub Vec, ); impl Default for $wrapper_ty { fn default() -> Self { Self(Vec::new()) } } impls! { $wrapper_ty ( Vec < T > ), $tag } }; } macro_rules! impls { ($wrapper_ty:ident ( $wrapped_ty:ident ), $tag:expr) => { impl $crate::wrapper::Asn1Type for $wrapper_ty { const TAG: Tag = $tag; const NAME: &'static str = stringify!($wrapper_ty); } impl From<$wrapped_ty> for $wrapper_ty { fn from(wrapped: $wrapped_ty) -> Self { Self(wrapped) } } impl From<$wrapper_ty> for $wrapped_ty { fn from(wrapper: $wrapper_ty) -> $wrapped_ty { wrapper.0 } } impl Deref for $wrapper_ty { type Target = $wrapped_ty; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for $wrapper_ty { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl PartialEq<$wrapped_ty> for $wrapper_ty { fn eq(&self, other: &$wrapped_ty) -> bool { self.0.eq(other) } } }; ($wrapper_ty:ident < $generic:ident >, $tag:expr) => { impl<$generic> $crate::wrapper::Asn1Type for $wrapper_ty<$generic> { const TAG: Tag = $tag; const NAME: &'static str = stringify!($wrapper_ty); } impl<$generic> Default for $wrapper_ty<$generic> where $generic: Default, { fn default() -> Self { Self($generic::default()) } } impl<$generic> From<$generic> for $wrapper_ty<$generic> { fn from(wrapped: $generic) -> Self { Self(wrapped) } } //-- Into cannot be defined to convert into a generic type (E0119) -- impl<$generic> Deref for $wrapper_ty<$generic> { type Target = $generic; fn deref(&self) -> &Self::Target { &self.0 } } impl<$generic> DerefMut for $wrapper_ty<$generic> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl<$generic> PartialEq<$generic> for $wrapper_ty<$generic> where $generic: PartialEq, { fn eq(&self, other: &$generic) -> bool { self.0.eq(other) } } }; ($wrapper_ty:ident ( $wrapped_ty:ident < $generic:ident > ), $tag:expr) => { impl<$generic> $crate::wrapper::Asn1Type for $wrapper_ty<$generic> { const TAG: Tag = $tag; const NAME: &'static str = stringify!($wrapper_ty); } impl<$generic> From<$wrapped_ty<$generic>> for $wrapper_ty<$generic> { fn from(wrapped: $wrapped_ty<$generic>) -> Self { Self(wrapped) } } impl<$generic> From<$wrapper_ty<$generic>> for $wrapped_ty<$generic> { fn from(wrapper: $wrapper_ty<$generic>) -> Self { wrapper.0 } } impl<$generic> Deref for $wrapper_ty<$generic> { type Target = $wrapped_ty<$generic>; fn deref(&self) -> &Self::Target { &self.0 } } impl<$generic> DerefMut for $wrapper_ty<$generic> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl<$generic> PartialEq<$wrapped_ty<$generic>> for $wrapper_ty<$generic> where $generic: PartialEq, { fn eq(&self, other: &$wrapped_ty<$generic>) -> bool { self.0.eq(other) } } }; } macro_rules! define_special_tag { ( $name:ident => $tag:expr ) => { asn1_wrapper! { special tag struct $name, $tag } }; ( $( $name:ident => $tag:expr , )+ ) => { $( define_special_tag! { $name => $tag } )+ }; } asn1_wrapper! { auto struct BitStringAsn1(BitString), Tag::BIT_STRING } asn1_wrapper! { auto struct ObjectIdentifierAsn1(ObjectIdentifier), Tag::OID } asn1_wrapper! { auto struct Utf8StringAsn1(Utf8String), Tag::UTF8_STRING } asn1_wrapper! { auto struct NumericStringAsn1(NumericString), Tag::NUMERIC_STRING } asn1_wrapper! { auto struct PrintableStringAsn1(PrintableString), Tag::PRINTABLE_STRING } asn1_wrapper! { auto struct IA5StringAsn1(IA5String), Tag::IA5_STRING } asn1_wrapper! { auto struct BMPStringAsn1(BMPString), Tag::BMP_STRING } asn1_wrapper! { auto struct UTCTimeAsn1(UTCTime), Tag::UTC_TIME } asn1_wrapper! { auto struct GeneralizedTimeAsn1(GeneralizedTime), Tag::GENERALIZED_TIME } // [RFC 4120 5.2.1](https://www.rfc-editor.org/rfc/rfc4120.txt) // Kerberos specification declares General String as IA5String // ```not-rust // KerberosString ::= GeneralString (IA5String) // ``` asn1_wrapper! { auto struct GeneralStringAsn1(IA5String), Tag::GENERAL_STRING } asn1_wrapper! { auto collection struct Asn1SequenceOf, Tag::SEQUENCE } asn1_wrapper! { auto collection struct Asn1SetOf, Tag::SET } define_special_tag! { ExplicitContextTag0 => Tag::context_specific_constructed(0), ExplicitContextTag1 => Tag::context_specific_constructed(1), ExplicitContextTag2 => Tag::context_specific_constructed(2), ExplicitContextTag3 => Tag::context_specific_constructed(3), ExplicitContextTag4 => Tag::context_specific_constructed(4), ExplicitContextTag5 => Tag::context_specific_constructed(5), ExplicitContextTag6 => Tag::context_specific_constructed(6), ExplicitContextTag7 => Tag::context_specific_constructed(7), ExplicitContextTag8 => Tag::context_specific_constructed(8), ExplicitContextTag9 => Tag::context_specific_constructed(9), ExplicitContextTag10 => Tag::context_specific_constructed(10), ExplicitContextTag11 => Tag::context_specific_constructed(11), ExplicitContextTag12 => Tag::context_specific_constructed(12), ExplicitContextTag13 => Tag::context_specific_constructed(13), ExplicitContextTag14 => Tag::context_specific_constructed(14), ExplicitContextTag15 => Tag::context_specific_constructed(15), ImplicitContextTag0 => Tag::context_specific_primitive(0), ImplicitContextTag1 => Tag::context_specific_primitive(1), ImplicitContextTag2 => Tag::context_specific_primitive(2), ImplicitContextTag3 => Tag::context_specific_primitive(3), ImplicitContextTag4 => Tag::context_specific_primitive(4), ImplicitContextTag5 => Tag::context_specific_primitive(5), ImplicitContextTag6 => Tag::context_specific_primitive(6), ImplicitContextTag7 => Tag::context_specific_primitive(7), ImplicitContextTag8 => Tag::context_specific_primitive(8), ImplicitContextTag9 => Tag::context_specific_primitive(9), ImplicitContextTag10 => Tag::context_specific_primitive(10), ImplicitContextTag11 => Tag::context_specific_primitive(11), ImplicitContextTag12 => Tag::context_specific_primitive(12), ImplicitContextTag13 => Tag::context_specific_primitive(13), ImplicitContextTag14 => Tag::context_specific_primitive(14), ImplicitContextTag15 => Tag::context_specific_primitive(15), } #[allow(clippy::derivable_impls)] impl Default for BitStringAsn1 { fn default() -> Self { BitStringAsn1(BitString::default()) } } impl Default for IA5StringAsn1 { fn default() -> Self { IA5StringAsn1::from(IA5String::default()) } } impl Default for BMPStringAsn1 { fn default() -> Self { BMPStringAsn1::from(BMPString::default()) } } fn serialize_vec(elems: &[T], serializer: S) -> Result<::Ok, ::Error> where S: ser::Serializer, T: Serialize, { use serde::ser::SerializeSeq; let mut seq = serializer.serialize_seq(Some(elems.len()))?; for e in elems { seq.serialize_element(e)?; } seq.end() } fn deserialize_vec<'de, D, T>(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, T: Deserialize<'de>, { struct Visitor(std::marker::PhantomData); impl<'de, T> de::Visitor<'de> for Visitor where T: Deserialize<'de>, { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid sequence of T") } fn visit_seq(self, mut seq: A) -> Result where A: de::SeqAccess<'de>, { let mut vec = Vec::new(); while let Some(e) = seq.next_element()? { vec.push(e); } Ok(vec) } } deserializer.deserialize_seq(Visitor(std::marker::PhantomData)) } /// A Vec wrapper for Asn1 encoding as OctetString. #[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Hash, Clone, Default)] pub struct OctetStringAsn1(#[serde(with = "serde_bytes")] pub Vec); type VecU8 = Vec; impls! { OctetStringAsn1(VecU8), Tag::OCTET_STRING } impl fmt::Debug for OctetStringAsn1 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "OctetString(0x")?; self.0.iter().try_for_each(|byte| write!(f, "{byte:02X}"))?; write!(f, ")")?; Ok(()) } } /// A BigInt wrapper for Asn1 encoding. /// /// Simply use primitive integer types if you don't need big integer. /// /// For underlying implementation, /// see this [Microsoft's documentation](https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-integer). #[derive(Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Hash, Clone)] pub struct IntegerAsn1(#[serde(with = "serde_bytes")] pub Vec); impls! { IntegerAsn1(VecU8), Tag::INTEGER } // X.690-0207 Section 8.3.2: fn minimal_encode_start(bytes: &[u8]) -> usize { let mut start = 0; while start + 1 < bytes.len() { if bytes[start] == 0 && (bytes[start + 1] & 0x80) == 0 || bytes[start] == 0xFF && (bytes[start + 1] & 0x80) == 0x80 { start += 1; } else { break; } } start } impl IntegerAsn1 { pub fn is_positive(&self) -> bool { if self.0.len() > 1 && self.0[0] == 0x00 || self.0.is_empty() { true } else { self.0[0] & 0x80 == 0 } } pub fn is_negative(&self) -> bool { if self.0.len() > 1 && self.0[0] == 0x00 { false } else if self.0.is_empty() { true } else { self.0[0] & 0x80 != 0 } } pub fn as_unsigned_bytes_be(&self) -> &[u8] { if self.0.len() > 1 { if self.0[0] == 0x00 { &self.0[1..] } else { &self.0 } } else if self.0.is_empty() { &[0] } else { &self.0 } } pub fn as_signed_bytes_be(&self) -> &[u8] { if self.0.is_empty() { &[0] } else { &self.0 } } pub fn from_bytes_be_signed(bytes: Vec) -> Self { let start = minimal_encode_start(&bytes); if start == 0 { Self(bytes) } else { Self(bytes[start..].to_vec()) } } /// Build an ASN.1 Integer from unsigned big endian bytes. /// /// If high order bit is set to 1, this shift all elements to the right /// and add a leading 0x00 byte indicating the number is positive. /// Prefer `from_signed_bytes_be` if you can build a signed bytes string without /// overhead on you side. pub fn from_bytes_be_unsigned(mut bytes: Vec) -> Self { if !bytes.is_empty() && bytes[0] & 0x80 == 0x80 { bytes.insert(0, 0x00); } let start = minimal_encode_start(&bytes); if start == 0 { Self(bytes) } else { Self(bytes[start..].to_vec()) } } } #[cfg(feature = "zeroize")] impl zeroize::Zeroize for IntegerAsn1 { fn zeroize(&mut self) { self.0.zeroize(); } } impl fmt::Debug for IntegerAsn1 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Integer(0x")?; self.0.iter().try_for_each(|byte| write!(f, "{byte:02X}"))?; write!(f, ")")?; Ok(()) } } /// A wrapper encoding/decoding only the header of the provided Asn1Wrapper with a length of 0. /// /// Examples: /// ``` /// use picky_asn1::wrapper::{ExplicitContextTag0, HeaderOnly}; /// use serde::{Serialize, Deserialize}; /// /// let tag_only = HeaderOnly::>::default(); /// let buffer = [0xA0, 0x00]; /// /// let encoded = picky_asn1_der::to_vec(&tag_only).expect("couldn't serialize"); /// assert_eq!( /// encoded, /// buffer, /// ); /// /// let decoded: HeaderOnly> = picky_asn1_der::from_bytes(&buffer).expect("couldn't deserialize"); /// assert_eq!( /// decoded, /// tag_only, /// ); /// ``` #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Hash, Clone, Default)] pub struct HeaderOnly( #[serde( serialize_with = "serialize_header_only", deserialize_with = "deserialize_header_only", bound(serialize = "T: Asn1Type", deserialize = "T: Asn1Type") )] pub std::marker::PhantomData, ); impl Asn1Type for HeaderOnly { const TAG: Tag = T::TAG; const NAME: &'static str = "HeaderOnly"; } #[allow(clippy::trivially_copy_pass_by_ref)] fn serialize_header_only( _: &std::marker::PhantomData, serializer: S, ) -> Result<::Ok, ::Error> where S: ser::Serializer, Phantom: Asn1Type, { serializer.serialize_bytes(&[Phantom::TAG.inner(), 0x00][..]) } fn deserialize_header_only<'de, D, Phantom>(deserializer: D) -> Result, D::Error> where D: de::Deserializer<'de>, Phantom: Asn1Type, { struct Visitor(std::marker::PhantomData); impl<'de, T> de::Visitor<'de> for Visitor where T: Asn1Type, { type Value = std::marker::PhantomData; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid header for empty payload") } fn visit_bytes(self, v: &[u8]) -> Result where E: de::Error, { if v.len() != 2 { return Err(E::invalid_value( de::Unexpected::Other("invalid ASN.1 header length"), &"a valid buffer representing an ASN.1 header with empty payload (two bytes)", )); } if v[0] != T::TAG.inner() { return Err(E::invalid_value( de::Unexpected::Other("invalid ASN.1 header: wrong tag"), &"a valid buffer representing an empty ASN.1 header (two bytes) with the expected tag", )); } if v[1] != 0 { return Err(E::invalid_value( de::Unexpected::Other("invalid ASN.1 header: bad payload length"), &"a valid buffer representing an empty ASN.1 header (two bytes) with no payload", )); } Ok(std::marker::PhantomData) } } deserializer.deserialize_bytes(Visitor(std::marker::PhantomData)) } /// A BitString encapsulating things. /// /// Same as `OctetStringAsn1Container` but using a BitString instead. /// /// Useful to perform a full serialization / deserialization in one pass /// instead of using `BitStringAsn1` manually. /// /// Examples /// ``` /// use picky_asn1::wrapper::BitStringAsn1Container; /// use serde::{Serialize, Deserialize}; /// /// #[derive(Serialize, Deserialize, Debug, PartialEq)] /// struct MyType { /// a: u32, /// b: u16, /// c: u16, /// } /// /// type MyTypeEncapsulated = BitStringAsn1Container; /// /// let encapsulated: MyTypeEncapsulated = MyType { /// a: 83910, /// b: 3839, /// c: 4023, /// }.into(); /// /// let buffer = [ /// 0x03, 0x10, 0x00, // bit string part /// 0x30, 0x0d, // sequence /// 0x02, 0x03, 0x01, 0x47, 0xc6, // integer a /// 0x02, 0x02, 0x0e, 0xff, // integer b /// 0x02, 0x02, 0x0f, 0xb7, // integer c /// ]; /// /// let encoded = picky_asn1_der::to_vec(&encapsulated).expect("couldn't serialize"); /// assert_eq!( /// encoded, /// buffer, /// ); /// /// let decoded: MyTypeEncapsulated = picky_asn1_der::from_bytes(&buffer).expect("couldn't deserialize"); /// assert_eq!( /// decoded, /// encapsulated, /// ); /// ``` #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Hash, Clone)] pub struct BitStringAsn1Container(pub Encapsulated); impls! { BitStringAsn1Container, Tag::BIT_STRING } /// An OctetString encapsulating things. /// /// Same as `BitStringAsn1Container` but using an OctetString instead. /// /// Useful to perform a full serialization / deserialization in one pass /// instead of using `OctetStringAsn1` manually. /// /// Examples /// ``` /// use picky_asn1::wrapper::OctetStringAsn1Container; /// use serde::{Serialize, Deserialize}; /// /// #[derive(Serialize, Deserialize, Debug, PartialEq)] /// struct MyType { /// a: u32, /// b: u16, /// c: u16, /// } /// /// type MyTypeEncapsulated = OctetStringAsn1Container; /// /// let encapsulated: MyTypeEncapsulated = MyType { /// a: 83910, /// b: 3839, /// c: 4023, /// }.into(); /// /// let buffer = [ /// 0x04, 0x0F, // octet string part /// 0x30, 0x0d, // sequence /// 0x02, 0x03, 0x01, 0x47, 0xc6, // integer a /// 0x02, 0x02, 0x0e, 0xff, // integer b /// 0x02, 0x02, 0x0f, 0xb7, // integer c /// ]; /// /// let encoded = picky_asn1_der::to_vec(&encapsulated).expect("couldn't serialize"); /// assert_eq!( /// encoded, /// buffer, /// ); /// /// let decoded: MyTypeEncapsulated = picky_asn1_der::from_bytes(&buffer).expect("couldn't deserialize"); /// assert_eq!( /// decoded, /// encapsulated, /// ); /// ``` #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Hash, Clone)] pub struct OctetStringAsn1Container(pub Encapsulated); impls! { OctetStringAsn1Container, Tag::OCTET_STRING } /// Wrapper for ASN.1 optionals fields /// /// Wrapped type has to implement the Default trait to be deserializable (on deserialization failure /// a default value is returned). /// /// Examples: /// ``` /// use picky_asn1::wrapper::{Optional, ExplicitContextTag0}; /// use serde::{Serialize, Deserialize}; /// /// #[derive(Serialize, Deserialize, Debug, PartialEq)] /// struct MyWrapper(u8); /// /// impl Default for MyWrapper { /// fn default() -> Self { /// Self(10) /// } /// } /// /// #[derive(Serialize, Deserialize, Debug, PartialEq)] /// struct ComplexType { /// // skip if default to reduce encoded size /// #[serde(skip_serializing_if = "optional_field_is_default")] /// optional_field: Optional, /// // behind application tag 0 to distinguish from optional_field that is a ASN.1 integer too. /// explicit_field: ExplicitContextTag0, /// } /// /// fn optional_field_is_default(wrapper: &Optional) -> bool { /// wrapper.0 == MyWrapper::default() /// } /// /// let complex_type = ComplexType { /// optional_field: MyWrapper::default().into(), /// explicit_field: 5.into(), /// }; /// /// let buffer = [ /// 0x30, 0x05, // sequence /// // optional field isn't present /// 0xA0, 0x03, 0x02, 0x01, 0x05, // explicit field /// ]; /// /// let encoded = picky_asn1_der::to_vec(&complex_type).expect("couldn't serialize"); /// assert_eq!( /// encoded, /// buffer, /// ); /// /// let decoded: ComplexType = picky_asn1_der::from_bytes(&buffer).expect("couldn't deserialize"); /// assert_eq!( /// decoded, /// complex_type, /// ); /// ``` #[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Clone)] pub struct Optional(pub T); impl Default for Optional { fn default() -> Self { Optional(T::default()) } } impl Optional where T: Default + PartialEq, { pub fn is_default(&self) -> bool { self.0 == T::default() } } impl From for Optional { fn from(wrapped: T) -> Self { Self(wrapped) } } impl Deref for Optional { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Optional { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl PartialEq for Optional where T: PartialEq, { fn eq(&self, other: &T) -> bool { self.0.eq(other) } } impl Serialize for Optional where T: Serialize, { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { self.0.serialize(serializer) } } impl<'de, T> Deserialize<'de> for Optional where T: Deserialize<'de> + Default, { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { struct Visitor(std::marker::PhantomData); impl<'de, T> de::Visitor<'de> for Visitor where T: Deserialize<'de>, { type Value = Optional; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "nothing or a valid DER-encoded T") } fn visit_newtype_struct(self, deserializer: D) -> Result where D: de::Deserializer<'de>, { T::deserialize(deserializer).map(Optional::from) } } match deserializer.deserialize_newtype_struct("Optional", Visitor(std::marker::PhantomData)) { Err(_) => Ok(Self(T::default())), result => result, } } } #[cfg(test)] mod tests { use super::*; #[test] fn integer_from_unsigned_bytes_be_no_panic() { IntegerAsn1::from_bytes_be_unsigned(vec![]); } #[test] fn minimal_encode_start_positive() { // Open question: shouldn't we replace a zero length "integer" with // 0x00 octet? assert_eq!(minimal_encode_start(b""), 0); assert_eq!(minimal_encode_start(b"\x00"), 0); assert_eq!(minimal_encode_start(b"\x00\x00"), 1); assert_eq!(minimal_encode_start(b"\x00\x00\x00"), 2); assert_eq!(minimal_encode_start(b"\x00\x00\x80"), 1); } #[test] fn minimal_encode_start_negative() { assert_eq!(minimal_encode_start(b"\xFF"), 0); assert_eq!(minimal_encode_start(b"\xFF\x00"), 0); assert_eq!(minimal_encode_start(b"\xFF\x80"), 1); assert_eq!(minimal_encode_start(b"\xFF\xFF\x00"), 1); assert_eq!(minimal_encode_start(b"\xFF\xFF\x80"), 2); } }