1+ use std:: sync:: { Arc , LazyLock , Mutex } ;
2+
3+ use arc_swap:: ArcSwapOption ;
14use cove_util:: encryption:: Cryptor ;
25
36use crate :: error:: CsppError ;
@@ -7,20 +10,64 @@ use crate::store::CsppStore;
710const MASTER_KEY_NAME : & str = "cspp::v1::master_key" ;
811const MASTER_KEY_ENCRYPTION_KEY_AND_NONCE : & str = "cspp::v1::master_key_encryption_key_and_nonce" ;
912
13+ static INIT_LOCK : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
14+ static MASTER_KEY_CACHE : LazyLock < ArcSwapOption < [ u8 ; 32 ] > > =
15+ LazyLock :: new ( || ArcSwapOption :: new ( None ) ) ;
16+
1017pub struct Cspp < S : CsppStore > ( S ) ;
1118
1219impl < S : CsppStore > Cspp < S > {
1320 pub fn new ( store : S ) -> Self {
1421 Self ( store)
1522 }
1623
24+ /// Loads the master key from the store, or generates and saves a new one
25+ ///
26+ /// Uses double-checked locking to prevent a TOCTOU race where two threads
27+ /// could both observe no key and generate different master keys
28+ pub fn get_or_create_master_key ( & self ) -> Result < MasterKey , CsppError > {
29+ // fast path (lock-free): return cached key
30+ if let Some ( bytes) = MASTER_KEY_CACHE . load ( ) . as_deref ( ) {
31+ return Ok ( MasterKey :: from_bytes ( * bytes) ) ;
32+ }
33+
34+ // slow path: acquire init lock for double-checked initialization
35+ let _guard = INIT_LOCK . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
36+
37+ // re-check cache after acquiring lock
38+ if let Some ( bytes) = MASTER_KEY_CACHE . load ( ) . as_deref ( ) {
39+ return Ok ( MasterKey :: from_bytes ( * bytes) ) ;
40+ }
41+
42+ // try loading from store
43+ if let Some ( key) = self . get_master_key ( ) ? {
44+ MASTER_KEY_CACHE . store ( Some ( Arc :: new ( * key. as_bytes ( ) ) ) ) ;
45+ return Ok ( key) ;
46+ }
47+
48+ // generate and save new key
49+ let key = MasterKey :: generate ( ) ;
50+ self . save_master_key ( & key) ?;
51+ MASTER_KEY_CACHE . store ( Some ( Arc :: new ( * key. as_bytes ( ) ) ) ) ;
52+
53+ Ok ( key)
54+ }
55+
56+ /// Deletes the master key from the store
57+ pub fn delete_master_key ( & self ) -> bool {
58+ let _guard = INIT_LOCK . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
59+ MASTER_KEY_CACHE . store ( None ) ;
60+ self . 0 . delete ( MASTER_KEY_NAME . into ( ) ) ;
61+ self . 0 . delete ( MASTER_KEY_ENCRYPTION_KEY_AND_NONCE . into ( ) )
62+ }
63+
1764 /// Saves the master key encrypted via the underlying store
1865 ///
1966 /// The master key is encrypted with a random [`Cryptor`] before storage. The
2067 /// keychain already provides at-rest encryption, but this extra layer prevents
2168 /// the plaintext key from being accidentally exposed if other code enumerates
2269 /// keychain entries — it must be explicitly decrypted to be read
23- pub fn save_master_key ( & self , master_key : & MasterKey ) -> Result < ( ) , CsppError > {
70+ fn save_master_key ( & self , master_key : & MasterKey ) -> Result < ( ) , CsppError > {
2471 let hex = hex:: encode ( master_key. as_bytes ( ) ) ;
2572 let cryptor = Cryptor :: new ( ) ;
2673
@@ -40,8 +87,8 @@ impl<S: CsppStore> Cspp<S> {
4087 Ok ( ( ) )
4188 }
4289
43- /// Loads the master key from the store , returns None if not found
44- pub fn get_master_key ( & self ) -> Result < Option < MasterKey > , CsppError > {
90+ /// Loads the master key, returns None if not found
91+ fn get_master_key ( & self ) -> Result < Option < MasterKey > , CsppError > {
4592 let Some ( encryption_secret) = self . 0 . get ( MASTER_KEY_ENCRYPTION_KEY_AND_NONCE . into ( ) ) else {
4693 return Ok ( None ) ;
4794 } ;
@@ -65,21 +112,9 @@ impl<S: CsppStore> Cspp<S> {
65112 Ok ( Some ( MasterKey :: from_bytes ( bytes) ) )
66113 }
67114
68- /// Deletes the master key from the store
69- pub fn delete_master_key ( & self ) -> bool {
70- self . 0 . delete ( MASTER_KEY_NAME . into ( ) ) ;
71- self . 0 . delete ( MASTER_KEY_ENCRYPTION_KEY_AND_NONCE . into ( ) )
72- }
73-
74- /// Loads the master key from the store, or generates and saves a new one
75- pub fn get_or_create_master_key ( & self ) -> Result < MasterKey , CsppError > {
76- if let Some ( key) = self . get_master_key ( ) ? {
77- return Ok ( key) ;
78- }
79-
80- let key = MasterKey :: generate ( ) ;
81- self . save_master_key ( & key) ?;
82- Ok ( key)
115+ #[ cfg( test) ]
116+ fn reset_cache ( ) {
117+ MASTER_KEY_CACHE . store ( None ) ;
83118 }
84119}
85120
@@ -90,6 +125,9 @@ mod tests {
90125
91126 use super :: * ;
92127
128+ // serializes tests that touch the global MASTER_KEY_CACHE
129+ static CACHE_TEST_LOCK : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
130+
93131 #[ derive( Debug ) ]
94132 struct MockStore ( Mutex < HashMap < String , String > > ) ;
95133
@@ -122,6 +160,9 @@ mod tests {
122160
123161 #[ test]
124162 fn master_key_store_and_load_roundtrip ( ) {
163+ let _guard = CACHE_TEST_LOCK . lock ( ) . unwrap ( ) ;
164+ Cspp :: < MockStore > :: reset_cache ( ) ;
165+
125166 let cspp = mock_cspp ( ) ;
126167 let original = MasterKey :: generate ( ) ;
127168 let original_bytes = * original. as_bytes ( ) ;
@@ -141,6 +182,9 @@ mod tests {
141182
142183 #[ test]
143184 fn get_or_create_creates_when_missing ( ) {
185+ let _guard = CACHE_TEST_LOCK . lock ( ) . unwrap ( ) ;
186+ Cspp :: < MockStore > :: reset_cache ( ) ;
187+
144188 let cspp = mock_cspp ( ) ;
145189 let first = cspp. get_or_create_master_key ( ) . unwrap ( ) ;
146190 let first_bytes = * first. as_bytes ( ) ;
@@ -151,6 +195,9 @@ mod tests {
151195
152196 #[ test]
153197 fn get_or_create_reuses_existing ( ) {
198+ let _guard = CACHE_TEST_LOCK . lock ( ) . unwrap ( ) ;
199+ Cspp :: < MockStore > :: reset_cache ( ) ;
200+
154201 let cspp = mock_cspp ( ) ;
155202 let original = MasterKey :: generate ( ) ;
156203 let original_bytes = * original. as_bytes ( ) ;
0 commit comments