1
1
//! Contains types and functions to generate and sign certificate authorities
2
2
//! (CAs).
3
- use std:: str:: FromStr ;
3
+ use std:: { fmt :: Debug , str:: FromStr } ;
4
4
5
5
use const_oid:: db:: rfc5280:: { ID_KP_CLIENT_AUTH , ID_KP_SERVER_AUTH } ;
6
6
use k8s_openapi:: api:: core:: v1:: Secret ;
@@ -9,9 +9,10 @@ use snafu::{OptionExt, ResultExt, Snafu};
9
9
use stackable_operator:: { client:: Client , commons:: secret:: SecretReference , time:: Duration } ;
10
10
use tracing:: { debug, instrument} ;
11
11
use x509_cert:: {
12
+ Certificate ,
12
13
builder:: { Builder , CertificateBuilder , Profile } ,
13
- der:: { DecodePem , pem:: LineEnding , referenced:: OwnedToRef } ,
14
- ext:: pkix:: { AuthorityKeyIdentifier , ExtendedKeyUsage } ,
14
+ der:: { DecodePem , asn1 :: Ia5String , pem:: LineEnding , referenced:: OwnedToRef } ,
15
+ ext:: pkix:: { AuthorityKeyIdentifier , ExtendedKeyUsage , SubjectAltName , name :: GeneralName } ,
15
16
name:: Name ,
16
17
serial_number:: SerialNumber ,
17
18
spki:: { EncodePublicKey , SubjectPublicKeyInfoOwned } ,
@@ -66,14 +67,20 @@ pub enum Error {
66
67
67
68
#[ snafu( display( "failed to parse AuthorityKeyIdentifier" ) ) ]
68
69
ParseAuthorityKeyIdentifier { source : x509_cert:: der:: Error } ,
70
+
71
+ #[ snafu( display( "The subject alternative DNS name \" {dns_name}\" is not a Ia5String" ) ) ]
72
+ SaDnsNameNotAIa5String {
73
+ dns_name : String ,
74
+ source : x509_cert:: der:: Error ,
75
+ } ,
69
76
}
70
77
71
78
/// Custom implementation of [`std::cmp::PartialEq`] because some inner types
72
79
/// don't implement it.
73
80
///
74
- /// Note that this implementation is restritced to testing because there is a
81
+ /// Note that this implementation is restricted to testing because there is a
75
82
/// variant that is impossible to compare, and will cause a panic if it is
76
- /// attemped .
83
+ /// attempted .
77
84
#[ cfg( test) ]
78
85
impl PartialEq for Error {
79
86
fn eq ( & self , other : & Self ) -> bool {
@@ -170,7 +177,7 @@ where
170
177
/// These parameters include:
171
178
///
172
179
/// - a randomly generated serial number
173
- /// - a default validity of one hour (see [`DEFAULT_CA_VALIDITY_SECONDS `])
180
+ /// - a default validity of one hour (see [`DEFAULT_CA_VALIDITY `])
174
181
///
175
182
/// The CA contains the public half of the provided `signing_key` and is
176
183
/// signed by the private half of said key.
@@ -181,9 +188,8 @@ where
181
188
#[ instrument( name = "create_certificate_authority" , skip( signing_key_pair) ) ]
182
189
pub fn new ( signing_key_pair : S ) -> Result < Self > {
183
190
let serial_number = rand:: random :: < u64 > ( ) ;
184
- let validity = Duration :: from_secs ( DEFAULT_CA_VALIDITY_SECONDS ) ;
185
191
186
- Self :: new_with ( signing_key_pair, serial_number, validity )
192
+ Self :: new_with ( signing_key_pair, serial_number, DEFAULT_CA_VALIDITY )
187
193
}
188
194
189
195
/// Creates a new CA certificate.
@@ -200,8 +206,8 @@ where
200
206
// We don't allow customization of the CA subject by callers. Every CA
201
207
// created by us should contain the same subject consisting a common set
202
208
// of distinguished names (DNs).
203
- let subject = Name :: from_str ( ROOT_CA_SUBJECT ) . context ( ParseSubjectSnafu {
204
- subject : ROOT_CA_SUBJECT ,
209
+ let subject = Name :: from_str ( SDP_ROOT_CA_SUBJECT ) . context ( ParseSubjectSnafu {
210
+ subject : SDP_ROOT_CA_SUBJECT ,
205
211
} ) ?;
206
212
207
213
let spki_pem = signing_key_pair
@@ -267,15 +273,16 @@ where
267
273
/// authentication, because they include [`ID_KP_CLIENT_AUTH`] and
268
274
/// [`ID_KP_SERVER_AUTH`] in the extended key usage extension.
269
275
///
270
- /// It is also possible to directly greate RSA or ECDSA-based leaf
276
+ /// It is also possible to directly create RSA or ECDSA-based leaf
271
277
/// certificates using [`CertificateAuthority::generate_rsa_leaf_certificate`]
272
278
/// and [`CertificateAuthority::generate_ecdsa_leaf_certificate`].
273
279
#[ instrument( skip( self , key_pair) ) ]
274
- pub fn generate_leaf_certificate < T > (
280
+ pub fn generate_leaf_certificate < ' a , T > (
275
281
& mut self ,
276
282
key_pair : T ,
277
283
name : & str ,
278
284
scope : & str ,
285
+ subject_alterative_dns_names : impl IntoIterator < Item = & ' a str > + Debug ,
279
286
validity : Duration ,
280
287
) -> Result < CertificatePair < T > >
281
288
where
@@ -301,10 +308,6 @@ where
301
308
let spki = SubjectPublicKeyInfoOwned :: from_pem ( spki_pem. as_bytes ( ) )
302
309
. context ( DecodeSpkiFromPemSnafu ) ?;
303
310
304
- // The leaf certificate can be used for WWW client and server
305
- // authentication. This is a base requirement for TLS certs.
306
- let eku = ExtendedKeyUsage ( vec ! [ ID_KP_CLIENT_AUTH , ID_KP_SERVER_AUTH ] ) ;
307
-
308
311
let signer = self . certificate_pair . key_pair . signing_key ( ) ;
309
312
let mut builder = CertificateBuilder :: new (
310
313
Profile :: Leaf {
@@ -325,9 +328,27 @@ where
325
328
)
326
329
. context ( CreateCertificateBuilderSnafu ) ?;
327
330
328
- // Again, add the extension created above.
331
+ // The leaf certificate can be used for WWW client and server
332
+ // authentication. This is a base requirement for TLS certs.
329
333
builder
330
- . add_extension ( & eku)
334
+ . add_extension ( & ExtendedKeyUsage ( vec ! [
335
+ ID_KP_CLIENT_AUTH ,
336
+ ID_KP_SERVER_AUTH ,
337
+ ] ) )
338
+ . context ( AddCertificateExtensionSnafu ) ?;
339
+
340
+ let sans = subject_alterative_dns_names
341
+ . into_iter ( )
342
+ . map ( |dns_name| {
343
+ Ok ( GeneralName :: DnsName ( Ia5String :: new ( dns_name) . context (
344
+ SaDnsNameNotAIa5StringSnafu {
345
+ dns_name : dns_name. to_string ( ) ,
346
+ } ,
347
+ ) ?) )
348
+ } )
349
+ . collect :: < Result < Vec < _ > , Error > > ( ) ?;
350
+ builder
351
+ . add_extension ( & SubjectAltName ( sans) )
331
352
. context ( AddCertificateExtensionSnafu ) ?;
332
353
333
354
debug ! ( "create and sign leaf certificate" ) ;
@@ -344,29 +365,31 @@ where
344
365
/// See [`CertificateAuthority::generate_leaf_certificate`] for more
345
366
/// information.
346
367
#[ instrument( skip( self ) ) ]
347
- pub fn generate_rsa_leaf_certificate (
368
+ pub fn generate_rsa_leaf_certificate < ' a > (
348
369
& mut self ,
349
370
name : & str ,
350
371
scope : & str ,
372
+ subject_alterative_dns_names : impl IntoIterator < Item = & ' a str > + Debug ,
351
373
validity : Duration ,
352
374
) -> Result < CertificatePair < rsa:: SigningKey > > {
353
375
let key = rsa:: SigningKey :: new ( ) . context ( GenerateRsaSigningKeySnafu ) ?;
354
- self . generate_leaf_certificate ( key, name, scope, validity)
376
+ self . generate_leaf_certificate ( key, name, scope, subject_alterative_dns_names , validity)
355
377
}
356
378
357
379
/// Generates an ECDSAasync -based leaf certificate which is signed by this CA.
358
380
///
359
381
/// See [`CertificateAuthority::generate_leaf_certificate`] for more
360
382
/// information.
361
383
#[ instrument( skip( self ) ) ]
362
- pub fn generate_ecdsa_leaf_certificate (
384
+ pub fn generate_ecdsa_leaf_certificate < ' a > (
363
385
& mut self ,
364
386
name : & str ,
365
387
scope : & str ,
388
+ subject_alterative_dns_names : impl IntoIterator < Item = & ' a str > + Debug ,
366
389
validity : Duration ,
367
390
) -> Result < CertificatePair < ecdsa:: SigningKey > > {
368
391
let key = ecdsa:: SigningKey :: new ( ) . context ( GenerateEcdsaSigningKeySnafu ) ?;
369
- self . generate_leaf_certificate ( key, name, scope, validity)
392
+ self . generate_leaf_certificate ( key, name, scope, subject_alterative_dns_names , validity)
370
393
}
371
394
372
395
/// Create a [`CertificateAuthority`] from a Kubernetes [`Secret`].
@@ -443,6 +466,11 @@ where
443
466
444
467
Self :: from_secret ( secret, key_certificate, key_private_key)
445
468
}
469
+
470
+ /// Returns the ca certificate.
471
+ pub fn ca_cert ( & self ) -> & Certificate {
472
+ & self . certificate_pair . certificate
473
+ }
446
474
}
447
475
448
476
impl CertificateAuthority < rsa:: SigningKey > {
@@ -468,19 +496,57 @@ fn format_leaf_certificate_subject(name: &str, scope: &str) -> Result<Name> {
468
496
469
497
#[ cfg( test) ]
470
498
mod tests {
499
+ use const_oid:: ObjectIdentifier ;
500
+
471
501
use super :: * ;
472
502
503
+ const TEST_CERT_LIFETIME : Duration = Duration :: from_hours_unchecked ( 1 ) ;
504
+ const TEST_SAN : & str = "airflow-0.airflow.default.svc.cluster.local" ;
505
+
473
506
#[ tokio:: test]
474
507
async fn rsa_key_generation ( ) {
475
508
let mut ca = CertificateAuthority :: new_rsa ( ) . unwrap ( ) ;
476
- ca. generate_rsa_leaf_certificate ( "Airflow" , "pod" , Duration :: from_secs ( 3600 ) )
477
- . unwrap ( ) ;
509
+ let cert = ca
510
+ . generate_rsa_leaf_certificate ( "Airflow" , "pod" , [ TEST_SAN ] , TEST_CERT_LIFETIME )
511
+ . expect ( "RSA certificate generation failed" ) ;
512
+
513
+ assert_cert_attributes ( cert. certificate ( ) ) ;
478
514
}
479
515
480
516
#[ tokio:: test]
481
517
async fn ecdsa_key_generation ( ) {
482
518
let mut ca = CertificateAuthority :: new_ecdsa ( ) . unwrap ( ) ;
483
- ca. generate_ecdsa_leaf_certificate ( "Airflow" , "pod" , Duration :: from_secs ( 3600 ) )
484
- . unwrap ( ) ;
519
+ let cert = ca
520
+ . generate_ecdsa_leaf_certificate ( "Airflow" , "pod" , [ TEST_SAN ] , TEST_CERT_LIFETIME )
521
+ . expect ( "ecdsa certificate generation failed" ) ;
522
+
523
+ assert_cert_attributes ( cert. certificate ( ) ) ;
524
+ }
525
+
526
+ fn assert_cert_attributes ( cert : & Certificate ) {
527
+ let cert = & cert. tbs_certificate ;
528
+ // Test subject
529
+ assert_eq ! (
530
+ cert. subject,
531
+ Name :: from_str( "CN=Airflow Certificate for pod" ) . unwrap( )
532
+ ) ;
533
+
534
+ // Test SAN extension is present
535
+ let extensions = cert. extensions . as_ref ( ) . expect ( "cert had no extension" ) ;
536
+ assert ! (
537
+ extensions
538
+ . iter( )
539
+ . any( |ext| ext. extn_id == ObjectIdentifier :: new_unwrap( "2.5.29.17" ) )
540
+ ) ;
541
+
542
+ // Test lifetime
543
+ let not_before = cert. validity . not_before . to_system_time ( ) ;
544
+ let not_after = cert. validity . not_after . to_system_time ( ) ;
545
+ assert_eq ! (
546
+ not_after
547
+ . duration_since( not_before)
548
+ . expect( "Failed to calculate duration between notBefore and notAfter" ) ,
549
+ * TEST_CERT_LIFETIME
550
+ ) ;
485
551
}
486
552
}
0 commit comments