Files
cli/vendor/rcgen/src/string.rs

667 lines
18 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! ASN.1 string types
use std::fmt;
use std::str::FromStr;
use crate::{Error, InvalidAsn1String};
/// ASN.1 `PrintableString` type.
///
/// Supports a subset of the ASCII printable characters (described below).
///
/// For the full ASCII character set, use
/// [`Ia5String`][`crate::Ia5String`].
///
/// # Examples
///
/// You can create a `PrintableString` from [a literal string][`&str`] with [`PrintableString::try_from`]:
///
/// ```
/// use rcgen::string::PrintableString;
/// let hello = PrintableString::try_from("hello").unwrap();
/// ```
///
/// # Supported characters
///
/// PrintableString is a subset of the [ASCII printable characters].
/// For instance, `'@'` is a printable character as per ASCII but can't be part of [ASN.1's `PrintableString`].
///
/// The following ASCII characters/ranges are supported:
///
/// - `A..Z`
/// - `a..z`
/// - `0..9`
/// - "` `" (i.e. space)
/// - `\`
/// - `(`
/// - `)`
/// - `+`
/// - `,`
/// - `-`
/// - `.`
/// - `/`
/// - `:`
/// - `=`
/// - `?`
///
/// [ASCII printable characters]: https://en.wikipedia.org/wiki/ASCII#Printable_characters
/// [ASN.1's `PrintableString`]: https://en.wikipedia.org/wiki/PrintableString
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct PrintableString(String);
impl PrintableString {
/// Extracts a string slice containing the entire `PrintableString`.
pub fn as_str(&self) -> &str {
&self.0
}
}
impl TryFrom<&str> for PrintableString {
type Error = Error;
/// Converts a `&str` to a [`PrintableString`].
///
/// Any character not in the [`PrintableString`] charset will be rejected.
/// See [`PrintableString`] documentation for more information.
///
/// The result is allocated on the heap.
fn try_from(input: &str) -> Result<Self, Error> {
input.to_string().try_into()
}
}
impl TryFrom<String> for PrintableString {
type Error = Error;
/// Converts a [`String`][`std::string::String`] into a [`PrintableString`]
///
/// Any character not in the [`PrintableString`] charset will be rejected.
/// See [`PrintableString`] documentation for more information.
///
/// This conversion does not allocate or copy memory.
fn try_from(value: String) -> Result<Self, Self::Error> {
for &c in value.as_bytes() {
match c {
b'A'..=b'Z'
| b'a'..=b'z'
| b'0'..=b'9'
| b' '
| b'\''
| b'('
| b')'
| b'+'
| b','
| b'-'
| b'.'
| b'/'
| b':'
| b'='
| b'?' => (),
_ => {
return Err(Error::InvalidAsn1String(
InvalidAsn1String::PrintableString(value),
))
},
}
}
Ok(Self(value))
}
}
impl FromStr for PrintableString {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl AsRef<str> for PrintableString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for PrintableString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl PartialEq<str> for PrintableString {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<String> for PrintableString {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<&str> for PrintableString {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<&String> for PrintableString {
fn eq(&self, other: &&String) -> bool {
self.as_str() == other.as_str()
}
}
/// ASN.1 `IA5String` type.
///
/// # Examples
///
/// You can create a `Ia5String` from [a literal string][`&str`] with [`Ia5String::try_from`]:
///
/// ```
/// use rcgen::string::Ia5String;
/// let hello = Ia5String::try_from("hello").unwrap();
/// ```
///
/// # Supported characters
///
/// Supports the [International Alphabet No. 5 (IA5)] character encoding, i.e.
/// the 128 characters of the ASCII alphabet. (Note: IA5 is now
/// technically known as the International Reference Alphabet or IRA as
/// specified in the ITU-T's T.50 recommendation).
///
/// For UTF-8, use [`String`][`std::string::String`].
///
/// [International Alphabet No. 5 (IA5)]: https://en.wikipedia.org/wiki/T.50_(standard)
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Ia5String(String);
impl Ia5String {
/// Extracts a string slice containing the entire `Ia5String`.
pub fn as_str(&self) -> &str {
&self.0
}
}
impl TryFrom<&str> for Ia5String {
type Error = Error;
/// Converts a `&str` to a [`Ia5String`].
///
/// Any character not in the [`Ia5String`] charset will be rejected.
/// See [`Ia5String`] documentation for more information.
///
/// The result is allocated on the heap.
fn try_from(input: &str) -> Result<Self, Error> {
input.to_string().try_into()
}
}
impl TryFrom<String> for Ia5String {
type Error = Error;
/// Converts a [`String`][`std::string::String`] into a [`Ia5String`]
///
/// Any character not in the [`Ia5String`] charset will be rejected.
/// See [`Ia5String`] documentation for more information.
fn try_from(input: String) -> Result<Self, Error> {
if !input.is_ascii() {
return Err(Error::InvalidAsn1String(InvalidAsn1String::Ia5String(
input,
)));
}
Ok(Self(input))
}
}
impl FromStr for Ia5String {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl AsRef<str> for Ia5String {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for Ia5String {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl PartialEq<str> for Ia5String {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<String> for Ia5String {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<&str> for Ia5String {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<&String> for Ia5String {
fn eq(&self, other: &&String) -> bool {
self.as_str() == other.as_str()
}
}
/// ASN.1 `TeletexString` type.
///
/// # Examples
///
/// You can create a `TeletexString` from [a literal string][`&str`] with [`TeletexString::try_from`]:
///
/// ```
/// use rcgen::string::TeletexString;
/// let hello = TeletexString::try_from("hello").unwrap();
/// ```
///
/// # Supported characters
///
/// The standard defines a complex character set allowed in this type. However, quoting the ASN.1
/// [mailing list], "a sizable volume of software in the world treats TeletexString (T61String) as a
/// simple 8-bit string with mostly Windows Latin 1 (superset of iso-8859-1) encoding".
///
/// `TeletexString` is included for backward compatibility, [RFC 5280] say it
/// SHOULD NOT be used for certificates for new subjects.
///
/// [mailing list]: https://www.mail-archive.com/asn1@asn1.org/msg00460.html
/// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct TeletexString(String);
impl TeletexString {
/// Extracts a string slice containing the entire `TeletexString`.
pub fn as_str(&self) -> &str {
&self.0
}
/// Returns a byte slice of this `TeletexString`s contents.
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl TryFrom<&str> for TeletexString {
type Error = Error;
/// Converts a `&str` to a [`TeletexString`].
///
/// Any character not in the [`TeletexString`] charset will be rejected.
/// See [`TeletexString`] documentation for more information.
///
/// The result is allocated on the heap.
fn try_from(input: &str) -> Result<Self, Error> {
input.to_string().try_into()
}
}
impl TryFrom<String> for TeletexString {
type Error = Error;
/// Converts a [`String`][`std::string::String`] into a [`TeletexString`]
///
/// Any character not in the [`TeletexString`] charset will be rejected.
/// See [`TeletexString`] documentation for more information.
///
/// This conversion does not allocate or copy memory.
fn try_from(input: String) -> Result<Self, Error> {
// Check all bytes are visible
if !input.as_bytes().iter().all(|b| (0x20..=0x7f).contains(b)) {
return Err(Error::InvalidAsn1String(InvalidAsn1String::TeletexString(
input,
)));
}
Ok(Self(input))
}
}
impl FromStr for TeletexString {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl AsRef<str> for TeletexString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for TeletexString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl PartialEq<str> for TeletexString {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<String> for TeletexString {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<&str> for TeletexString {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<&String> for TeletexString {
fn eq(&self, other: &&String) -> bool {
self.as_str() == other.as_str()
}
}
/// ASN.1 `BMPString` type.
///
/// # Examples
///
/// You can create a `BmpString` from [a literal string][`&str`] with [`BmpString::try_from`]:
///
/// ```
/// use rcgen::string::BmpString;
/// let hello = BmpString::try_from("hello").unwrap();
/// ```
///
/// # Supported characters
///
/// Encodes Basic Multilingual Plane (BMP) subset of Unicode (ISO 10646),
/// a.k.a. UCS-2.
///
/// Bytes are encoded as UTF-16 big-endian.
///
/// `BMPString` is included for backward compatibility, [RFC 5280] say it
/// SHOULD NOT be used for certificates for new subjects.
///
/// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct BmpString(Vec<u8>);
impl BmpString {
/// Returns a byte slice of this `BmpString`'s contents.
///
/// The inverse of this method is [`from_utf16be`].
///
/// [`from_utf16be`]: BmpString::from_utf16be
///
/// # Examples
///
/// ```
/// use rcgen::string::BmpString;
/// let s = BmpString::try_from("hello").unwrap();
///
/// assert_eq!(&[0, 104, 0, 101, 0, 108, 0, 108, 0, 111], s.as_bytes());
/// ```
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
/// Decode a UTF-16BEencoded vector `vec` into a `BmpString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data.
pub fn from_utf16be(vec: Vec<u8>) -> Result<Self, Error> {
if vec.len() % 2 != 0 {
return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString(
"Invalid UTF-16 encoding".to_string(),
)));
}
// FIXME: Update this when `array_chunks` is stabilized.
for maybe_char in char::decode_utf16(
vec.chunks_exact(2)
.map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])),
) {
// We check we only use the BMP subset of Unicode (the first 65 536 code points)
match maybe_char {
// Character is in the Basic Multilingual Plane
Ok(c) if (c as u64) < u64::from(u16::MAX) => (),
// Characters outside Basic Multilingual Plane or unpaired surrogates
_ => {
return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString(
"Invalid UTF-16 encoding".to_string(),
)));
},
}
}
Ok(Self(vec.to_vec()))
}
}
impl TryFrom<&str> for BmpString {
type Error = Error;
/// Converts a `&str` to a [`BmpString`].
///
/// Any character not in the [`BmpString`] charset will be rejected.
/// See [`BmpString`] documentation for more information.
///
/// The result is allocated on the heap.
fn try_from(value: &str) -> Result<Self, Self::Error> {
let capacity = value.len().checked_mul(2).ok_or_else(|| {
Error::InvalidAsn1String(InvalidAsn1String::BmpString(value.to_string()))
})?;
let mut bytes = Vec::with_capacity(capacity);
for code_point in value.encode_utf16() {
bytes.extend(code_point.to_be_bytes());
}
BmpString::from_utf16be(bytes)
}
}
impl TryFrom<String> for BmpString {
type Error = Error;
/// Converts a [`String`][`std::string::String`] into a [`BmpString`]
///
/// Any character not in the [`BmpString`] charset will be rejected.
/// See [`BmpString`] documentation for more information.
///
/// Parsing a `BmpString` allocates memory since the UTF-8 to UTF-16 conversion requires a memory allocation.
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_str().try_into()
}
}
impl FromStr for BmpString {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
/// ASN.1 `UniversalString` type.
///
/// # Examples
///
/// You can create a `UniversalString` from [a literal string][`&str`] with [`UniversalString::try_from`]:
///
/// ```
/// use rcgen::string::UniversalString;
/// let hello = UniversalString::try_from("hello").unwrap();
/// ```
///
/// # Supported characters
///
/// The characters which can appear in the `UniversalString` type are any of the characters allowed by
/// ISO/IEC 10646 (Unicode).
///
/// Bytes are encoded like UTF-32 big-endian.
///
/// `UniversalString` is included for backward compatibility, [RFC 5280] say it
/// SHOULD NOT be used for certificates for new subjects.
///
/// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct UniversalString(Vec<u8>);
impl UniversalString {
/// Returns a byte slice of this `UniversalString`'s contents.
///
/// The inverse of this method is [`from_utf32be`].
///
/// [`from_utf32be`]: UniversalString::from_utf32be
///
/// # Examples
///
/// ```
/// use rcgen::string::UniversalString;
/// let s = UniversalString::try_from("hello").unwrap();
///
/// assert_eq!(&[0, 0, 0, 104, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111], s.as_bytes());
/// ```
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
/// Decode a UTF-32BEencoded vector `vec` into a `UniversalString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data.
pub fn from_utf32be(vec: Vec<u8>) -> Result<UniversalString, Error> {
if vec.len() % 4 != 0 {
return Err(Error::InvalidAsn1String(
InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()),
));
}
// FIXME: Update this when `array_chunks` is stabilized.
for maybe_char in vec
.chunks_exact(4)
.map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
{
if core::char::from_u32(maybe_char).is_none() {
return Err(Error::InvalidAsn1String(
InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()),
));
}
}
Ok(Self(vec))
}
}
impl TryFrom<&str> for UniversalString {
type Error = Error;
/// Converts a `&str` to a [`UniversalString`].
///
/// Any character not in the [`UniversalString`] charset will be rejected.
/// See [`UniversalString`] documentation for more information.
///
/// The result is allocated on the heap.
fn try_from(value: &str) -> Result<Self, Self::Error> {
let capacity = value.len().checked_mul(4).ok_or_else(|| {
Error::InvalidAsn1String(InvalidAsn1String::UniversalString(value.to_string()))
})?;
let mut bytes = Vec::with_capacity(capacity);
// A `char` is any Unicode code point other than a surrogate code point.
// The code units for UTF-32 correspond exactly to Unicode code points.
// (https://www.unicode.org/reports/tr19/tr19-9.html#Introduction)
// So any `char` is a valid UTF-32, we just cast it to perform the conversion.
for char in value.chars().map(|char| char as u32) {
bytes.extend(char.to_be_bytes())
}
UniversalString::from_utf32be(bytes)
}
}
impl TryFrom<String> for UniversalString {
type Error = Error;
/// Converts a [`String`][`std::string::String`] into a [`UniversalString`]
///
/// Any character not in the [`UniversalString`] charset will be rejected.
/// See [`UniversalString`] documentation for more information.
///
/// Parsing a `UniversalString` allocates memory since the UTF-8 to UTF-32 conversion requires a memory allocation.
fn try_from(value: String) -> Result<Self, Self::Error> {
value.as_str().try_into()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use crate::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString};
#[test]
fn printable_string() {
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let printable_string = PrintableString::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(printable_string, EXAMPLE_UTF8);
assert!(PrintableString::try_from("@").is_err());
assert!(PrintableString::try_from("*").is_err());
}
#[test]
fn ia5_string() {
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let ia5_string = Ia5String::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(ia5_string, EXAMPLE_UTF8);
assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok());
assert!(Ia5String::try_from(String::from('\u{8F}')).is_err());
}
#[test]
fn teletext_string() {
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let teletext_string = TeletexString::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(teletext_string, EXAMPLE_UTF8);
assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok());
assert!(Ia5String::try_from(String::from('\u{8F}')).is_err());
}
#[test]
fn bmp_string() {
const EXPECTED_BYTES: &[u8] = &[
0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69,
0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x54, 0x00, 0x65, 0x00, 0x6d,
0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65,
];
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let bmp_string = BmpString::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(bmp_string.as_bytes(), EXPECTED_BYTES);
assert!(BmpString::try_from(String::from('\u{FFFE}')).is_ok());
assert!(BmpString::try_from(String::from('\u{FFFF}')).is_err());
}
#[test]
fn universal_string() {
const EXPECTED_BYTES: &[u8] = &[
0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00,
0x00, 0x74, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x69,
0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00,
0x00, 0x65, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6d,
0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00,
0x00, 0x74, 0x00, 0x00, 0x00, 0x65,
];
const EXAMPLE_UTF8: &str = "CertificateTemplate";
let universal_string = UniversalString::try_from(EXAMPLE_UTF8).unwrap();
assert_eq!(universal_string.as_bytes(), EXPECTED_BYTES);
}
}