14
14
15
15
use std:: borrow:: Cow ;
16
16
17
- use anyhow:: Context ;
17
+ use anyhow:: { bail , Context } ;
18
18
use async_trait:: async_trait;
19
19
use camino:: Utf8PathBuf ;
20
20
use mas_jose:: jwk:: { JsonWebKey , JsonWebKeySet } ;
@@ -35,31 +35,23 @@ fn example_secret() -> &'static str {
35
35
"0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"
36
36
}
37
37
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
-
54
38
#[ derive( JsonSchema , Serialize , Deserialize , Clone , Debug ) ]
55
39
pub struct KeyConfig {
56
40
kid : String ,
57
41
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 > ,
60
51
61
- #[ serde( flatten) ]
62
- key : KeyOrFile ,
52
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
53
+ #[ schemars( with = "Option<String>" ) ]
54
+ key_file : Option < Utf8PathBuf > ,
63
55
}
64
56
65
57
/// Application secrets
@@ -90,25 +82,28 @@ impl SecretsConfig {
90
82
pub async fn key_store ( & self ) -> anyhow:: Result < Keystore > {
91
83
let mut keys = Vec :: with_capacity ( self . keys . len ( ) ) ;
92
84
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`" )
97
89
}
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 ?) ) ,
99
92
} ;
100
93
101
94
// 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 ) => {
104
99
// If the key was embedded in the config file, assume it is formatted as PEM
105
100
if let Some ( password) = password {
106
101
PrivateKey :: load_encrypted_pem ( key, password. as_bytes ( ) ) ?
107
102
} else {
108
103
PrivateKey :: load_pem ( key) ?
109
104
}
110
105
}
111
- KeyOrFile :: KeyFile ( path) => {
106
+ ( None , Some ( path) ) => {
112
107
// When reading from disk, it might be either PEM or DER. `PrivateKey::load*`
113
108
// will try both.
114
109
let key = tokio:: fs:: read ( path) . await ?;
@@ -161,7 +156,9 @@ impl ConfigurationSection for SecretsConfig {
161
156
let rsa_key = KeyConfig {
162
157
kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
163
158
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 ,
165
162
} ;
166
163
167
164
let span = tracing:: info_span!( "ec_p256" ) ;
@@ -177,7 +174,9 @@ impl ConfigurationSection for SecretsConfig {
177
174
let ec_p256_key = KeyConfig {
178
175
kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
179
176
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 ,
181
180
} ;
182
181
183
182
let span = tracing:: info_span!( "ec_p384" ) ;
@@ -193,7 +192,9 @@ impl ConfigurationSection for SecretsConfig {
193
192
let ec_p384_key = KeyConfig {
194
193
kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
195
194
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 ,
197
198
} ;
198
199
199
200
let span = tracing:: info_span!( "ec_k256" ) ;
@@ -209,7 +210,9 @@ impl ConfigurationSection for SecretsConfig {
209
210
let ec_k256_key = KeyConfig {
210
211
kid : Alphanumeric . sample_string ( & mut rng, 10 ) ,
211
212
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 ,
213
216
} ;
214
217
215
218
Ok ( Self {
@@ -218,11 +221,49 @@ impl ConfigurationSection for SecretsConfig {
218
221
} )
219
222
}
220
223
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
+
221
261
fn test ( ) -> Self {
222
262
let rsa_key = KeyConfig {
223
263
kid : "abcdef" . to_owned ( ) ,
224
264
password : None ,
225
- key : KeyOrFile :: Key (
265
+ password_file : None ,
266
+ key : Some (
226
267
indoc:: indoc! { r"
227
268
-----BEGIN PRIVATE KEY-----
228
269
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAymS2RkeIZo7pUeEN
@@ -237,11 +278,13 @@ impl ConfigurationSection for SecretsConfig {
237
278
" }
238
279
. to_owned ( ) ,
239
280
) ,
281
+ key_file : None ,
240
282
} ;
241
283
let ecdsa_key = KeyConfig {
242
284
kid : "ghijkl" . to_owned ( ) ,
243
285
password : None ,
244
- key : KeyOrFile :: Key (
286
+ password_file : None ,
287
+ key : Some (
245
288
indoc:: indoc! { r"
246
289
-----BEGIN PRIVATE KEY-----
247
290
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgqfn5mYO/5Qq/wOOiWgHA
@@ -251,6 +294,7 @@ impl ConfigurationSection for SecretsConfig {
251
294
" }
252
295
. to_owned ( ) ,
253
296
) ,
297
+ key_file : None ,
254
298
} ;
255
299
256
300
Self {
0 commit comments