@@ -24,6 +24,26 @@ use std::string::String;
2424#[ cfg( feature = "std" ) ]
2525use std:: vec:: Vec ;
2626
27+ /// A DNS Name suitable for use in the TLS Server Name Indication (SNI)
28+ /// extension and/or for use as the reference hostname for which to verify a
29+ /// certificate.
30+ pub enum GeneralDNSNameRef < ' name > {
31+ /// a valid DNS name
32+ DNSName ( DNSNameRef < ' name > ) ,
33+ /// a DNS name containing a wildcard
34+ Wildcard ( WildcardDNSNameRef < ' name > ) ,
35+ }
36+
37+ impl < ' a > From < GeneralDNSNameRef < ' a > > for & ' a str {
38+ fn from ( d : GeneralDNSNameRef < ' a > ) -> Self {
39+ match d {
40+ GeneralDNSNameRef :: DNSName ( name) => name. into ( ) ,
41+ GeneralDNSNameRef :: Wildcard ( name) => name. into ( ) ,
42+ }
43+ }
44+ }
45+
46+
2747/// A DNS Name suitable for use in the TLS Server Name Indication (SNI)
2848/// extension and/or for use as the reference hostname for which to verify a
2949/// certificate.
@@ -132,6 +152,104 @@ impl<'a> From<DNSNameRef<'a>> for &'a str {
132152 }
133153}
134154
155+ /// A reference to a DNS Name suitable for use in the TLS Server Name Indication
156+ /// (SNI) extension and/or for use as the reference hostname for which to verify
157+ /// a certificate. Compared to `DNSName`, this one will store domain names containing
158+ /// a wildcard.
159+ ///
160+ /// A `WildcardDNSName` is guaranteed to be syntactically valid. The validity rules are
161+ /// specified in [RFC 5280 Section 7.2], except that underscores are also
162+ /// allowed, and following [RFC 6125].
163+ ///
164+ /// `WildcardDNSName` stores a copy of the input it was constructed from in a `String`
165+ /// and so it is only available when the `std` default feature is enabled.
166+ ///
167+ /// `Eq`, `PartialEq`, etc. are not implemented because name comparison
168+ /// frequently should be done case-insensitively and/or with other caveats that
169+ /// depend on the specific circumstances in which the comparison is done.
170+ ///
171+ /// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
172+ /// [RFC 6125]: https://tools.ietf.org/html/rfc6125
173+ #[ cfg( feature = "std" ) ]
174+ #[ derive( Clone , Debug , Eq , PartialEq , Hash ) ]
175+ pub struct WildcardDNSName ( String ) ;
176+
177+ #[ cfg( feature = "std" ) ]
178+ impl WildcardDNSName {
179+ /// Returns a `WildcardDNSNameRef` that refers to this `WildcardDNSName`.
180+ pub fn as_ref ( & self ) -> WildcardDNSNameRef { WildcardDNSNameRef ( self . 0 . as_bytes ( ) ) }
181+ }
182+
183+ #[ cfg( feature = "std" ) ]
184+ impl AsRef < str > for WildcardDNSName {
185+ fn as_ref ( & self ) -> & str { self . 0 . as_ref ( ) }
186+ }
187+
188+ // Deprecated
189+ #[ cfg( feature = "std" ) ]
190+ impl From < WildcardDNSNameRef < ' _ > > for WildcardDNSName {
191+ fn from ( dns_name : WildcardDNSNameRef ) -> Self { dns_name. to_owned ( ) }
192+ }
193+
194+ /// A reference to a DNS Name suitable for use in the TLS Server Name Indication
195+ /// (SNI) extension and/or for use as the reference hostname for which to verify
196+ /// a certificate.
197+ ///
198+ /// A `WildcardDNSNameRef` is guaranteed to be syntactically valid. The validity rules
199+ /// are specified in [RFC 5280 Section 7.2], except that underscores are also
200+ /// allowed.
201+ ///
202+ /// `Eq`, `PartialEq`, etc. are not implemented because name comparison
203+ /// frequently should be done case-insensitively and/or with other caveats that
204+ /// depend on the specific circumstances in which the comparison is done.
205+ ///
206+ /// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
207+ #[ derive( Clone , Copy ) ]
208+ pub struct WildcardDNSNameRef < ' a > ( & ' a [ u8 ] ) ;
209+
210+ impl < ' a > WildcardDNSNameRef < ' a > {
211+ /// Constructs a `WildcardDNSNameRef` from the given input if the input is a
212+ /// syntactically-valid DNS name.
213+ pub fn try_from_ascii ( dns_name : & ' a [ u8 ] ) -> Result < Self , InvalidDNSNameError > {
214+ if !is_valid_wildcard_dns_id ( untrusted:: Input :: from ( dns_name) ) {
215+ return Err ( InvalidDNSNameError ) ;
216+ }
217+
218+ Ok ( Self ( dns_name) )
219+ }
220+
221+ /// Constructs a `WildcardDNSNameRef` from the given input if the input is a
222+ /// syntactically-valid DNS name.
223+ pub fn try_from_ascii_str ( dns_name : & ' a str ) -> Result < Self , InvalidDNSNameError > {
224+ Self :: try_from_ascii ( dns_name. as_bytes ( ) )
225+ }
226+
227+ /// Constructs a `WildcardDNSName` from this `WildcardDNSNameRef`
228+ #[ cfg( feature = "std" ) ]
229+ pub fn to_owned ( & self ) -> WildcardDNSName {
230+ // WildcardDNSNameRef is already guaranteed to be valid ASCII, which is a
231+ // subset of UTF-8.
232+ let s: & str = self . clone ( ) . into ( ) ;
233+ WildcardDNSName ( s. to_ascii_lowercase ( ) )
234+ }
235+ }
236+
237+ #[ cfg( feature = "std" ) ]
238+ impl core:: fmt:: Debug for WildcardDNSNameRef < ' _ > {
239+ fn fmt ( & self , f : & mut core:: fmt:: Formatter ) -> Result < ( ) , core:: fmt:: Error > {
240+ let lowercase = self . clone ( ) . to_owned ( ) ;
241+ f. debug_tuple ( "WildcardDNSNameRef" ) . field ( & lowercase. 0 ) . finish ( )
242+ }
243+ }
244+
245+ impl < ' a > From < WildcardDNSNameRef < ' a > > for & ' a str {
246+ fn from ( WildcardDNSNameRef ( d) : WildcardDNSNameRef < ' a > ) -> Self {
247+ // The unwrap won't fail because DNSNameRefs are guaranteed to be ASCII
248+ // and ASCII is a subset of UTF-8.
249+ core:: str:: from_utf8 ( d) . unwrap ( )
250+ }
251+ }
252+
135253pub fn verify_cert_dns_name (
136254 cert : & super :: EndEntityCert , DNSNameRef ( dns_name) : DNSNameRef ,
137255) -> Result < ( ) , Error > {
@@ -163,14 +281,15 @@ pub fn verify_cert_dns_name(
163281
164282#[ cfg( feature = "std" ) ]
165283pub fn list_cert_dns_names < ' names > ( cert : & super :: EndEntityCert < ' names > )
166- -> Result < Vec < DNSNameRef < ' names > > , Error > {
284+ -> Result < Vec < GeneralDNSNameRef < ' names > > , Error > {
167285 let cert = & cert. inner ;
168286 let names = std:: cell:: RefCell :: new ( Vec :: new ( ) ) ;
169287
170288 iterate_names ( cert. subject , cert. subject_alt_name , Ok ( ( ) ) , & |name| {
171289 match name {
172290 GeneralName :: DNSName ( presented_id) => {
173- match DNSNameRef :: try_from_ascii ( presented_id) {
291+ match DNSNameRef :: try_from_ascii ( presented_id. as_slice_less_safe ( ) ) . map ( GeneralDNSNameRef :: DNSName )
292+ . or_else ( |_| WildcardDNSNameRef :: try_from_ascii ( presented_id. as_slice_less_safe ( ) ) . map ( GeneralDNSNameRef :: Wildcard ) ) {
174293 Ok ( name) => names. borrow_mut ( ) . push ( name) ,
175294 Err ( _) => { /* keep going */ } ,
176295 } ;
@@ -783,6 +902,10 @@ fn is_valid_reference_dns_id(hostname: untrusted::Input) -> bool {
783902 is_valid_dns_id ( hostname, IDRole :: ReferenceID , AllowWildcards :: No )
784903}
785904
905+ fn is_valid_wildcard_dns_id ( hostname : untrusted:: Input ) -> bool {
906+ is_valid_dns_id ( hostname, IDRole :: ReferenceID , AllowWildcards :: Yes )
907+ }
908+
786909// https://tools.ietf.org/html/rfc5280#section-4.2.1.6:
787910//
788911// When the subjectAltName extension contains a domain name system
0 commit comments