1414
1515use std:: borrow:: Cow ;
1616
17- use anyhow:: Context ;
17+ use anyhow:: { bail , Context } ;
1818use async_trait:: async_trait;
1919use camino:: Utf8PathBuf ;
2020use mas_jose:: jwk:: { JsonWebKey , JsonWebKeySet } ;
@@ -35,31 +35,23 @@ fn example_secret() -> &'static str {
3535 "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"
3636}
3737
38- #[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
39- #[ serde( rename_all = "snake_case" ) ]
40- pub enum KeyOrFile {
41- Key ( String ) ,
42- #[ schemars( with = "String" ) ]
43- KeyFile ( Utf8PathBuf ) ,
44- }
45-
46- #[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
47- #[ serde( rename_all = "snake_case" ) ]
48- pub enum PasswordOrFile {
49- Password ( String ) ,
50- #[ schemars( with = "String" ) ]
51- PasswordFile ( Utf8PathBuf ) ,
52- }
53-
5438#[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
5539pub struct KeyConfig {
5640 kid : String ,
5741
58- #[ serde( flatten) ]
59- password : Option < PasswordOrFile > ,
42+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
43+ password : Option < String > ,
44+
45+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
46+ #[ schemars( with = "Option<String>" ) ]
47+ password_file : Option < Utf8PathBuf > ,
48+
49+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
50+ key : Option < String > ,
6051
61- #[ serde( flatten) ]
62- key : KeyOrFile ,
52+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
53+ #[ schemars( with = "Option<String>" ) ]
54+ key_file : Option < Utf8PathBuf > ,
6355}
6456
6557/// Application secrets
@@ -90,25 +82,28 @@ impl SecretsConfig {
9082 pub async fn key_store ( & self ) -> anyhow:: Result < Keystore > {
9183 let mut keys = Vec :: with_capacity ( self . keys . len ( ) ) ;
9284 for item in & self . keys {
93- let password = match & item. password {
94- Some ( PasswordOrFile :: Password ( password ) ) => Some ( Cow :: Borrowed ( password . as_str ( ) ) ) ,
95- Some ( PasswordOrFile :: PasswordFile ( path ) ) => {
96- Some ( Cow :: Owned ( tokio :: fs :: read_to_string ( path ) . await ? ) )
85+ let password = match ( & item. password , & item . password_file ) {
86+ ( None , None ) => None ,
87+ ( Some ( _ ) , Some ( _ ) ) => {
88+ bail ! ( "Cannot specify both `password` and `password_file`" )
9789 }
98- None => None ,
90+ ( Some ( password) , None ) => Some ( Cow :: Borrowed ( password) ) ,
91+ ( None , Some ( path) ) => Some ( Cow :: Owned ( tokio:: fs:: read_to_string ( path) . await ?) ) ,
9992 } ;
10093
10194 // Read the key either embedded in the config file or on disk
102- let key = match & item. key {
103- KeyOrFile :: Key ( key) => {
95+ let key = match ( & item. key , & item. key_file ) {
96+ ( None , None ) => bail ! ( "Missing `key` or `key_file`" ) ,
97+ ( Some ( _) , Some ( _) ) => bail ! ( "Cannot specify both `key` and `key_file`" ) ,
98+ ( Some ( key) , None ) => {
10499 // If the key was embedded in the config file, assume it is formatted as PEM
105100 if let Some ( password) = password {
106101 PrivateKey :: load_encrypted_pem ( key, password. as_bytes ( ) ) ?
107102 } else {
108103 PrivateKey :: load_pem ( key) ?
109104 }
110105 }
111- KeyOrFile :: KeyFile ( path) => {
106+ ( None , Some ( path) ) => {
112107 // When reading from disk, it might be either PEM or DER. `PrivateKey::load*`
113108 // will try both.
114109 let key = tokio:: fs:: read ( path) . await ?;
@@ -161,7 +156,9 @@ impl ConfigurationSection for SecretsConfig {
161156 let rsa_key = KeyConfig {
162157 kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
163158 password : None ,
164- key : KeyOrFile :: Key ( rsa_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
159+ password_file : None ,
160+ key : Some ( rsa_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
161+ key_file : None ,
165162 } ;
166163
167164 let span = tracing:: info_span!( "ec_p256" ) ;
@@ -177,7 +174,9 @@ impl ConfigurationSection for SecretsConfig {
177174 let ec_p256_key = KeyConfig {
178175 kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
179176 password : None ,
180- key : KeyOrFile :: Key ( ec_p256_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
177+ password_file : None ,
178+ key : Some ( ec_p256_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
179+ key_file : None ,
181180 } ;
182181
183182 let span = tracing:: info_span!( "ec_p384" ) ;
@@ -193,7 +192,9 @@ impl ConfigurationSection for SecretsConfig {
193192 let ec_p384_key = KeyConfig {
194193 kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
195194 password : None ,
196- key : KeyOrFile :: Key ( ec_p384_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
195+ password_file : None ,
196+ key : Some ( ec_p384_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
197+ key_file : None ,
197198 } ;
198199
199200 let span = tracing:: info_span!( "ec_k256" ) ;
@@ -209,7 +210,9 @@ impl ConfigurationSection for SecretsConfig {
209210 let ec_k256_key = KeyConfig {
210211 kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
211212 password : None ,
212- key : KeyOrFile :: Key ( ec_k256_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
213+ password_file : None ,
214+ key : Some ( ec_k256_key. to_pem ( pem_rfc7468:: LineEnding :: LF ) ?. to_string ( ) ) ,
215+ key_file : None ,
213216 } ;
214217
215218 Ok ( Self {
@@ -218,11 +221,49 @@ impl ConfigurationSection for SecretsConfig {
218221 } )
219222 }
220223
224+ fn validate ( & self , figment : & figment:: Figment ) -> Result < ( ) , figment:: Error > {
225+ for ( index, key) in self . keys . iter ( ) . enumerate ( ) {
226+ let annotate = |mut error : figment:: Error | {
227+ error. metadata = figment
228+ . find_metadata ( & format ! ( "{root}.keys" , root = Self :: PATH . unwrap( ) ) )
229+ . cloned ( ) ;
230+ error. profile = Some ( figment:: Profile :: Default ) ;
231+ error. path = vec ! [
232+ Self :: PATH . unwrap( ) . to_owned( ) ,
233+ "keys" . to_owned( ) ,
234+ index. to_string( ) ,
235+ ] ;
236+ Err ( error)
237+ } ;
238+
239+ if key. key . is_none ( ) && key. key_file . is_none ( ) {
240+ return annotate ( figment:: Error :: from (
241+ "Missing `key` or `key_file`" . to_owned ( ) ,
242+ ) ) ;
243+ }
244+
245+ if key. key . is_some ( ) && key. key_file . is_some ( ) {
246+ return annotate ( figment:: Error :: from (
247+ "Cannot specify both `key` and `key_file`" . to_owned ( ) ,
248+ ) ) ;
249+ }
250+
251+ if key. password . is_some ( ) && key. password_file . is_some ( ) {
252+ return annotate ( figment:: Error :: from (
253+ "Cannot specify both `password` and `password_file`" . to_owned ( ) ,
254+ ) ) ;
255+ }
256+ }
257+
258+ Ok ( ( ) )
259+ }
260+
221261 fn test ( ) -> Self {
222262 let rsa_key = KeyConfig {
223263 kid : "abcdef" . to_owned ( ) ,
224264 password : None ,
225- key : KeyOrFile :: Key (
265+ password_file : None ,
266+ key : Some (
226267 indoc:: indoc! { r"
227268 -----BEGIN PRIVATE KEY-----
228269 MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAymS2RkeIZo7pUeEN
@@ -237,11 +278,13 @@ impl ConfigurationSection for SecretsConfig {
237278 " }
238279 . to_owned ( ) ,
239280 ) ,
281+ key_file : None ,
240282 } ;
241283 let ecdsa_key = KeyConfig {
242284 kid : "ghijkl" . to_owned ( ) ,
243285 password : None ,
244- key : KeyOrFile :: Key (
286+ password_file : None ,
287+ key : Some (
245288 indoc:: indoc! { r"
246289 -----BEGIN PRIVATE KEY-----
247290 MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgqfn5mYO/5Qq/wOOiWgHA
@@ -251,6 +294,7 @@ impl ConfigurationSection for SecretsConfig {
251294 " }
252295 . to_owned ( ) ,
253296 ) ,
297+ key_file : None ,
254298 } ;
255299
256300 Self {
0 commit comments