1- use std:: fmt:: Debug ;
1+ use std:: { fmt:: Debug , net :: IpAddr } ;
22
33use bon:: Builder ;
44use const_oid:: db:: rfc5280:: { ID_KP_CLIENT_AUTH , ID_KP_SERVER_AUTH } ;
5252 #[ snafu( display( "failed to add certificate extension" ) ) ]
5353 AddCertificateExtension { source : x509_cert:: builder:: Error } ,
5454
55- #[ snafu( display( "The subject alternative DNS name \" {dns_name}\" is not a Ia5String" ) ) ]
56- SaDnsNameNotAIa5String {
57- dns_name : String ,
55+ #[ snafu( display(
56+ "failed to parse subject alternative DNS name \" {subject_alternative_dns_name}\" as a Ia5 string"
57+ ) ) ]
58+ ParseSubjectAlternativeDnsName {
59+ subject_alternative_dns_name : String ,
5860 source : x509_cert:: der:: Error ,
5961 } ,
6062
@@ -93,11 +95,16 @@ where
9395 /// Required subject of the certificate, usually starts with `CN=`.
9496 subject : & ' a str ,
9597
96- /// Optional list of subject alternative names (SAN) DNS entries,
98+ /// Optional list of subject alternative name DNS entries
9799 /// that are added to the certificate.
98100 #[ builder( default ) ]
99101 subject_alterative_dns_names : & ' a [ & ' a str ] ,
100102
103+ /// Optional list of subject alternative name IP address entries
104+ /// that are added to the certificate.
105+ #[ builder( default ) ]
106+ subject_alterative_ip_addresses : & ' a [ IpAddr ] ,
107+
101108 /// Validity/lifetime of the certificate.
102109 ///
103110 /// If not specified the default of [`DEFAULT_CERTIFICATE_VALIDITY`] will be used.
@@ -194,17 +201,23 @@ where
194201 ] ) )
195202 . context ( AddCertificateExtensionSnafu ) ?;
196203
197- let sans = self
198- . subject_alterative_dns_names
204+ let san_dns = self . subject_alterative_dns_names . iter ( ) . map ( |dns_name| {
205+ Ok ( GeneralName :: DnsName (
206+ Ia5String :: new ( dns_name) . with_context ( |_| ParseSubjectAlternativeDnsNameSnafu {
207+ subject_alternative_dns_name : dns_name. to_string ( ) ,
208+ } ) ?,
209+ ) )
210+ } ) ;
211+ let san_ips = self
212+ . subject_alterative_ip_addresses
199213 . iter ( )
200- . map ( |dns_name| {
201- Ok ( GeneralName :: DnsName ( Ia5String :: new ( dns_name) . context (
202- SaDnsNameNotAIa5StringSnafu {
203- dns_name : dns_name. to_string ( ) ,
204- } ,
205- ) ?) )
206- } )
214+ . copied ( )
215+ . map ( GeneralName :: from)
216+ . map ( Result :: Ok ) ;
217+ let sans = san_dns
218+ . chain ( san_ips)
207219 . collect :: < Result < Vec < _ > , CreateCertificateError < KP :: Error > > > ( ) ?;
220+
208221 builder
209222 . add_extension ( & SubjectAltName ( sans) )
210223 . context ( AddCertificateExtensionSnafu ) ?;
@@ -221,6 +234,8 @@ where
221234
222235#[ cfg( test) ]
223236mod tests {
237+ use std:: net:: { Ipv4Addr , Ipv6Addr } ;
238+
224239 use x509_cert:: {
225240 certificate:: TbsCertificateInner , der:: Decode , ext:: pkix:: ID_CE_SUBJECT_ALT_NAME ,
226241 } ;
@@ -247,6 +262,7 @@ mod tests {
247262 & certificate. certificate . tbs_certificate ,
248263 "CN=trino-coordinator-default-0" ,
249264 & [ ] ,
265+ & [ ] ,
250266 DEFAULT_CERTIFICATE_VALIDITY ,
251267 None ,
252268 ) ;
@@ -262,10 +278,12 @@ mod tests {
262278 "trino-coordinator-default-0.trino-coordinator-default.default.svc.cluster-local" ,
263279 "trino-coordinator-default.default.svc.cluster-local" ,
264280 ] ;
281+ let san_ips = [ "10.0.0.1" . parse ( ) . unwrap ( ) , "fe80::42" . parse ( ) . unwrap ( ) ] ;
265282
266283 let certificate = CertificateBuilder :: builder ( )
267284 . subject ( "CN=trino-coordinator-default-0" )
268285 . subject_alterative_dns_names ( & sans)
286+ . subject_alterative_ip_addresses ( & san_ips)
269287 . serial_number ( 08121997 )
270288 . validity ( Duration :: from_days_unchecked ( 42 ) )
271289 . key_pair ( rsa:: SigningKey :: new ( ) . unwrap ( ) )
@@ -277,6 +295,7 @@ mod tests {
277295 & certificate. certificate . tbs_certificate ,
278296 "CN=trino-coordinator-default-0" ,
279297 & sans,
298+ & san_ips,
280299 Duration :: from_days_unchecked ( 42 ) ,
281300 Some ( 08121997 ) ,
282301 ) ;
@@ -286,6 +305,7 @@ mod tests {
286305 certificate : & TbsCertificateInner ,
287306 subject : & str ,
288307 sans : & [ & str ] ,
308+ san_ips : & [ IpAddr ] ,
289309 validity : Duration ,
290310 serial_number : Option < u64 > ,
291311 ) {
@@ -300,17 +320,25 @@ mod tests {
300320 . find ( |ext| ext. extn_id == ID_CE_SUBJECT_ALT_NAME )
301321 . expect ( "cert had no SAN extension" ) ;
302322
303- let san_extension = SubjectAltName :: from_der ( san_extension. extn_value . as_bytes ( ) )
304- . expect ( "failed to parse SAN" ) ;
305- let actual_sans = san_extension
306- . 0
323+ let san_entries = SubjectAltName :: from_der ( san_extension. extn_value . as_bytes ( ) )
324+ . expect ( "failed to parse SAN" )
325+ . 0 ;
326+ let actual_sans = san_entries
307327 . iter ( )
308328 . filter_map ( |san| match san {
309329 GeneralName :: DnsName ( dns_name) => Some ( dns_name. as_str ( ) ) ,
310330 _ => None ,
311331 } )
312332 . collect :: < Vec < _ > > ( ) ;
313333 assert_eq ! ( actual_sans, sans) ;
334+ let actual_san_ips = san_entries
335+ . iter ( )
336+ . filter_map ( |san| match san {
337+ GeneralName :: IpAddress ( ip) => Some ( bytes_to_ip_addr ( ip. as_bytes ( ) ) ) ,
338+ _ => None ,
339+ } )
340+ . collect :: < Vec < _ > > ( ) ;
341+ assert_eq ! ( actual_san_ips, san_ips) ;
314342
315343 let not_before = certificate. validity . not_before . to_system_time ( ) ;
316344 let not_after = certificate. validity . not_after . to_system_time ( ) ;
@@ -327,4 +355,23 @@ mod tests {
327355 assert_ne ! ( certificate. serial_number, SerialNumber :: from( 0_u64 ) )
328356 }
329357 }
358+
359+ fn bytes_to_ip_addr ( bytes : & [ u8 ] ) -> IpAddr {
360+ match bytes. len ( ) {
361+ 4 => {
362+ let mut array = [ 0u8 ; 4 ] ;
363+ array. copy_from_slice ( bytes) ;
364+ IpAddr :: V4 ( Ipv4Addr :: from ( array) )
365+ }
366+ 16 => {
367+ let mut array = [ 0u8 ; 16 ] ;
368+ array. copy_from_slice ( bytes) ;
369+ IpAddr :: V6 ( Ipv6Addr :: from ( array) )
370+ }
371+ _ => panic ! (
372+ "Invalid IP byte length: expected 4 or 16, got {}" ,
373+ bytes. len( )
374+ ) ,
375+ }
376+ }
330377}
0 commit comments