1
- use std:: fmt:: Debug ;
1
+ use std:: { fmt:: Debug , net :: IpAddr } ;
2
2
3
3
use bon:: Builder ;
4
4
use const_oid:: db:: rfc5280:: { ID_KP_CLIENT_AUTH , ID_KP_SERVER_AUTH } ;
52
52
#[ snafu( display( "failed to add certificate extension" ) ) ]
53
53
AddCertificateExtension { source : x509_cert:: builder:: Error } ,
54
54
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 ,
58
60
source : x509_cert:: der:: Error ,
59
61
} ,
60
62
@@ -93,11 +95,16 @@ where
93
95
/// Required subject of the certificate, usually starts with `CN=`.
94
96
subject : & ' a str ,
95
97
96
- /// Optional list of subject alternative names (SAN) DNS entries,
98
+ /// Optional list of subject alternative name DNS entries
97
99
/// that are added to the certificate.
98
100
#[ builder( default ) ]
99
101
subject_alterative_dns_names : & ' a [ & ' a str ] ,
100
102
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
+
101
108
/// Validity/lifetime of the certificate.
102
109
///
103
110
/// If not specified the default of [`DEFAULT_CERTIFICATE_VALIDITY`] will be used.
@@ -194,17 +201,23 @@ where
194
201
] ) )
195
202
. context ( AddCertificateExtensionSnafu ) ?;
196
203
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
199
213
. 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)
207
219
. collect :: < Result < Vec < _ > , CreateCertificateError < KP :: Error > > > ( ) ?;
220
+
208
221
builder
209
222
. add_extension ( & SubjectAltName ( sans) )
210
223
. context ( AddCertificateExtensionSnafu ) ?;
@@ -221,6 +234,8 @@ where
221
234
222
235
#[ cfg( test) ]
223
236
mod tests {
237
+ use std:: net:: { Ipv4Addr , Ipv6Addr } ;
238
+
224
239
use x509_cert:: {
225
240
certificate:: TbsCertificateInner , der:: Decode , ext:: pkix:: ID_CE_SUBJECT_ALT_NAME ,
226
241
} ;
@@ -247,6 +262,7 @@ mod tests {
247
262
& certificate. certificate . tbs_certificate ,
248
263
"CN=trino-coordinator-default-0" ,
249
264
& [ ] ,
265
+ & [ ] ,
250
266
DEFAULT_CERTIFICATE_VALIDITY ,
251
267
None ,
252
268
) ;
@@ -262,10 +278,12 @@ mod tests {
262
278
"trino-coordinator-default-0.trino-coordinator-default.default.svc.cluster-local" ,
263
279
"trino-coordinator-default.default.svc.cluster-local" ,
264
280
] ;
281
+ let san_ips = [ "10.0.0.1" . parse ( ) . unwrap ( ) , "fe80::42" . parse ( ) . unwrap ( ) ] ;
265
282
266
283
let certificate = CertificateBuilder :: builder ( )
267
284
. subject ( "CN=trino-coordinator-default-0" )
268
285
. subject_alterative_dns_names ( & sans)
286
+ . subject_alterative_ip_addresses ( & san_ips)
269
287
. serial_number ( 08121997 )
270
288
. validity ( Duration :: from_days_unchecked ( 42 ) )
271
289
. key_pair ( rsa:: SigningKey :: new ( ) . unwrap ( ) )
@@ -277,6 +295,7 @@ mod tests {
277
295
& certificate. certificate . tbs_certificate ,
278
296
"CN=trino-coordinator-default-0" ,
279
297
& sans,
298
+ & san_ips,
280
299
Duration :: from_days_unchecked ( 42 ) ,
281
300
Some ( 08121997 ) ,
282
301
) ;
@@ -286,6 +305,7 @@ mod tests {
286
305
certificate : & TbsCertificateInner ,
287
306
subject : & str ,
288
307
sans : & [ & str ] ,
308
+ san_ips : & [ IpAddr ] ,
289
309
validity : Duration ,
290
310
serial_number : Option < u64 > ,
291
311
) {
@@ -300,17 +320,25 @@ mod tests {
300
320
. find ( |ext| ext. extn_id == ID_CE_SUBJECT_ALT_NAME )
301
321
. expect ( "cert had no SAN extension" ) ;
302
322
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
307
327
. iter ( )
308
328
. filter_map ( |san| match san {
309
329
GeneralName :: DnsName ( dns_name) => Some ( dns_name. as_str ( ) ) ,
310
330
_ => None ,
311
331
} )
312
332
. collect :: < Vec < _ > > ( ) ;
313
333
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) ;
314
342
315
343
let not_before = certificate. validity . not_before . to_system_time ( ) ;
316
344
let not_after = certificate. validity . not_after . to_system_time ( ) ;
@@ -327,4 +355,23 @@ mod tests {
327
355
assert_ne ! ( certificate. serial_number, SerialNumber :: from( 0_u64 ) )
328
356
}
329
357
}
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
+ }
330
377
}
0 commit comments