@@ -132,7 +132,12 @@ impl From<Key> for KeyRaw {
132132#[ serde_as]
133133#[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
134134pub 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 > ,
136141
137142 #[ schemars( with = "PasswordRaw" ) ]
138143 #[ serde_as( as = "serde_with::TryFromInto<PasswordRaw>" ) ]
@@ -178,12 +183,24 @@ impl KeyConfig {
178183 None => PrivateKey :: load ( & key) ?,
179184 } ;
180185
186+ let kid = match self . kid . clone ( ) {
187+ Some ( kid) => kid,
188+ None => kid_from_key ( & private_key) ?,
189+ } ;
190+
181191 Ok ( JsonWebKey :: new ( private_key)
182- . with_kid ( self . kid . clone ( ) )
192+ . with_kid ( kid)
183193 . with_use ( mas_iana:: jose:: JsonWebKeyUse :: Sig ) )
184194 }
185195}
186196
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+
187204/// Encryption config option.
188205#[ derive( Debug , Clone ) ]
189206pub enum Encryption {
@@ -322,7 +339,7 @@ impl SecretsConfig {
322339 . await
323340 . context ( "could not join blocking task" ) ?;
324341 let rsa_key = KeyConfig {
325- kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
342+ kid : Some ( Alphanumeric . sample_string ( & mut rng, 10 ) ) ,
326343 password : None ,
327344 key : Key :: Value ( rsa_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
328345 } ;
@@ -338,7 +355,7 @@ impl SecretsConfig {
338355 . await
339356 . context ( "could not join blocking task" ) ?;
340357 let ec_p256_key = KeyConfig {
341- kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
358+ kid : Some ( Alphanumeric . sample_string ( & mut rng, 10 ) ) ,
342359 password : None ,
343360 key : Key :: Value ( ec_p256_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
344361 } ;
@@ -354,7 +371,7 @@ impl SecretsConfig {
354371 . await
355372 . context ( "could not join blocking task" ) ?;
356373 let ec_p384_key = KeyConfig {
357- kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
374+ kid : Some ( Alphanumeric . sample_string ( & mut rng, 10 ) ) ,
358375 password : None ,
359376 key : Key :: Value ( ec_p384_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
360377 } ;
@@ -370,7 +387,7 @@ impl SecretsConfig {
370387 . await
371388 . context ( "could not join blocking task" ) ?;
372389 let ec_k256_key = KeyConfig {
373- kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
390+ kid : Some ( Alphanumeric . sample_string ( & mut rng, 10 ) ) ,
374391 password : None ,
375392 key : Key :: Value ( ec_k256_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
376393 } ;
@@ -383,7 +400,7 @@ impl SecretsConfig {
383400
384401 pub ( crate ) fn test ( ) -> Self {
385402 let rsa_key = KeyConfig {
386- kid : "abcdef" . to_owned ( ) ,
403+ kid : Some ( "abcdef" . to_owned ( ) ) ,
387404 password : None ,
388405 key : Key :: Value (
389406 indoc:: indoc! { r"
@@ -402,7 +419,7 @@ impl SecretsConfig {
402419 ) ,
403420 } ;
404421 let ecdsa_key = KeyConfig {
405- kid : "ghijkl" . to_owned ( ) ,
422+ kid : Some ( "ghijkl" . to_owned ( ) ) ,
406423 password : None ,
407424 key : Key :: Value (
408425 indoc:: indoc! { r"
@@ -422,3 +439,68 @@ impl SecretsConfig {
422439 }
423440 }
424441}
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