@@ -132,7 +132,12 @@ impl From<Key> for KeyRaw {
132
132
#[ serde_as]
133
133
#[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
134
134
pub struct KeyConfig {
135
- kid : String ,
135
+ /// The key ID `kid` of the key as used by JWKs.
136
+ ///
137
+ /// If not given, `kid` will be derived from the key by hex-encoding the
138
+ /// first four bytes of the key’s fingerprint.
139
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
140
+ kid : Option < String > ,
136
141
137
142
#[ schemars( with = "PasswordRaw" ) ]
138
143
#[ serde_as( as = "serde_with::TryFromInto<PasswordRaw>" ) ]
@@ -178,12 +183,24 @@ impl KeyConfig {
178
183
None => PrivateKey :: load ( & key) ?,
179
184
} ;
180
185
186
+ let kid = match self . kid . clone ( ) {
187
+ Some ( kid) => kid,
188
+ None => kid_from_key ( & private_key) ?,
189
+ } ;
190
+
181
191
Ok ( JsonWebKey :: new ( private_key)
182
- . with_kid ( self . kid . clone ( ) )
192
+ . with_kid ( kid)
183
193
. with_use ( mas_iana:: jose:: JsonWebKeyUse :: Sig ) )
184
194
}
185
195
}
186
196
197
+ /// Returns a kid derived from the given key.
198
+ fn kid_from_key ( private_key : & PrivateKey ) -> anyhow:: Result < String > {
199
+ let fingerprint = private_key. fingerprint ( ) ?;
200
+ let head = fingerprint. first_chunk :: < 4 > ( ) . unwrap ( ) ;
201
+ Ok ( hex:: encode ( head) )
202
+ }
203
+
187
204
/// Encryption config option.
188
205
#[ derive( Debug , Clone ) ]
189
206
pub enum Encryption {
@@ -322,7 +339,7 @@ impl SecretsConfig {
322
339
. await
323
340
. context ( "could not join blocking task" ) ?;
324
341
let rsa_key = KeyConfig {
325
- kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
342
+ kid : Some ( Alphanumeric . sample_string ( & mut rng, 10 ) ) ,
326
343
password : None ,
327
344
key : Key :: Value ( rsa_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
328
345
} ;
@@ -338,7 +355,7 @@ impl SecretsConfig {
338
355
. await
339
356
. context ( "could not join blocking task" ) ?;
340
357
let ec_p256_key = KeyConfig {
341
- kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
358
+ kid : Some ( Alphanumeric . sample_string ( & mut rng, 10 ) ) ,
342
359
password : None ,
343
360
key : Key :: Value ( ec_p256_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
344
361
} ;
@@ -354,7 +371,7 @@ impl SecretsConfig {
354
371
. await
355
372
. context ( "could not join blocking task" ) ?;
356
373
let ec_p384_key = KeyConfig {
357
- kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
374
+ kid : Some ( Alphanumeric . sample_string ( & mut rng, 10 ) ) ,
358
375
password : None ,
359
376
key : Key :: Value ( ec_p384_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
360
377
} ;
@@ -370,7 +387,7 @@ impl SecretsConfig {
370
387
. await
371
388
. context ( "could not join blocking task" ) ?;
372
389
let ec_k256_key = KeyConfig {
373
- kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
390
+ kid : Some ( Alphanumeric . sample_string ( & mut rng, 10 ) ) ,
374
391
password : None ,
375
392
key : Key :: Value ( ec_k256_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
376
393
} ;
@@ -383,7 +400,7 @@ impl SecretsConfig {
383
400
384
401
pub ( crate ) fn test ( ) -> Self {
385
402
let rsa_key = KeyConfig {
386
- kid : "abcdef" . to_owned ( ) ,
403
+ kid : Some ( "abcdef" . to_owned ( ) ) ,
387
404
password : None ,
388
405
key : Key :: Value (
389
406
indoc:: indoc! { r"
@@ -402,7 +419,7 @@ impl SecretsConfig {
402
419
) ,
403
420
} ;
404
421
let ecdsa_key = KeyConfig {
405
- kid : "ghijkl" . to_owned ( ) ,
422
+ kid : Some ( "ghijkl" . to_owned ( ) ) ,
406
423
password : None ,
407
424
key : Key :: Value (
408
425
indoc:: indoc! { r"
@@ -422,3 +439,68 @@ impl SecretsConfig {
422
439
}
423
440
}
424
441
}
442
+
443
+ #[ cfg( test) ]
444
+ mod tests {
445
+ use figment:: {
446
+ Figment , Jail ,
447
+ providers:: { Format , Yaml } ,
448
+ } ;
449
+ use mas_jose:: constraints:: Constrainable ;
450
+ use tokio:: { runtime:: Handle , task} ;
451
+
452
+ use super :: * ;
453
+
454
+ #[ tokio:: test]
455
+ async fn load_config_inline_secrets ( ) {
456
+ task:: spawn_blocking ( || {
457
+ Jail :: expect_with ( |jail| {
458
+ jail. create_file (
459
+ "config.yaml" ,
460
+ indoc:: indoc! { r"
461
+ secrets:
462
+ encryption: >-
463
+ 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff
464
+ keys:
465
+ - kid: lekid0
466
+ key: |
467
+ -----BEGIN EC PRIVATE KEY-----
468
+ MHcCAQEEIOtZfDuXZr/NC0V3sisR4Chf7RZg6a2dpZesoXMlsPeRoAoGCCqGSM49
469
+ AwEHoUQDQgAECfpqx64lrR85MOhdMxNmIgmz8IfmM5VY9ICX9aoaArnD9FjgkBIl
470
+ fGmQWxxXDSWH6SQln9tROVZaduenJqDtDw==
471
+ -----END EC PRIVATE KEY-----
472
+ - key: |
473
+ -----BEGIN EC PRIVATE KEY-----
474
+ MHcCAQEEIKlZz/GnH0idVH1PnAF4HQNwRafgBaE2tmyN1wjfdOQqoAoGCCqGSM49
475
+ AwEHoUQDQgAEHrgPeG+Mt8eahih1h4qaPjhl7jT25cdzBkg3dbVks6gBR2Rx4ug9
476
+ h27LAir5RqxByHvua2XsP46rSTChof78uw==
477
+ -----END EC PRIVATE KEY-----
478
+ " } ,
479
+ ) ?;
480
+
481
+ let config = Figment :: new ( )
482
+ . merge ( Yaml :: file ( "config.yaml" ) )
483
+ . extract_inner :: < SecretsConfig > ( "secrets" ) ?;
484
+
485
+ Handle :: current ( ) . block_on ( async move {
486
+ assert_eq ! (
487
+ config. encryption( ) . await . unwrap( ) ,
488
+ [
489
+ 0 , 0 , 17 , 17 , 34 , 34 , 51 , 51 , 68 , 68 , 85 , 85 , 102 , 102 , 119 , 119 , 136 ,
490
+ 136 , 153 , 153 , 170 , 170 , 187 , 187 , 204 , 204 , 221 , 221 , 238 , 238 , 255 ,
491
+ 255
492
+ ]
493
+ ) ;
494
+
495
+ let key_store = config. key_store ( ) . await . unwrap ( ) ;
496
+ assert ! ( key_store. iter( ) . any( |k| k. kid( ) == Some ( "lekid0" ) ) ) ;
497
+ assert ! ( key_store. iter( ) . any( |k| k. kid( ) == Some ( "040b0ab8" ) ) ) ;
498
+ } ) ;
499
+
500
+ Ok ( ( ) )
501
+ } ) ;
502
+ } )
503
+ . await
504
+ . unwrap ( ) ;
505
+ }
506
+ }
0 commit comments