@@ -111,7 +111,6 @@ impl VssStore {
111111 let mut entropy_seed = [ 0u8 ; 32 ] ;
112112 getrandom:: fill ( & mut entropy_seed) . expect ( "Failed to generate random bytes" ) ;
113113 let entropy_source = RandomBytes :: new ( entropy_seed) ;
114- let setup_entropy_source = RandomBytes :: new ( entropy_seed) ;
115114
116115 let setup_retry_policy = retry_policy ( ) ;
117116 let setup_client = VssClient :: new_with_headers (
@@ -126,19 +125,22 @@ impl VssStore {
126125
127126 let setup_store_id = store_id. clone ( ) ;
128127 let runtime_handle = internal_runtime. handle ( ) . clone ( ) ;
129- let schema_version = std:: thread:: spawn ( move || {
130- runtime_handle. block_on ( async {
131- determine_and_write_schema_version (
132- & setup_client,
133- & setup_store_id,
134- data_encryption_key,
135- & setup_key_obfuscator,
136- & setup_entropy_source,
137- )
138- . await
139- } )
128+ let schema_version = std:: thread:: scope ( |scope| {
129+ scope
130+ . spawn ( || {
131+ runtime_handle. block_on ( async {
132+ determine_and_write_schema_version (
133+ & setup_client,
134+ & setup_store_id,
135+ data_encryption_key,
136+ & setup_key_obfuscator,
137+ & entropy_source,
138+ )
139+ . await
140+ } )
141+ } )
142+ . join ( )
140143 } )
141- . join ( )
142144 . map_err ( |_| io:: Error :: new ( io:: ErrorKind :: Other , "VSS schema setup task panicked" ) ) ??;
143145
144146 let inner = Arc :: new ( VssStoreInner :: new (
@@ -1111,6 +1113,115 @@ impl VssStoreBuilder {
11111113 }
11121114}
11131115
1116+ #[ cfg( test) ]
1117+ mod nonce_tests {
1118+ use std:: io:: { Read , Write } ;
1119+ use std:: net:: { TcpListener , TcpStream } ;
1120+
1121+ use vss_client:: types:: { ErrorCode , ErrorResponse , ListKeyVersionsResponse , PutObjectResponse } ;
1122+
1123+ use super :: * ;
1124+
1125+ fn read_request ( stream : & mut TcpStream ) -> ( String , Vec < u8 > ) {
1126+ let mut request = Vec :: new ( ) ;
1127+ let header_end = loop {
1128+ let mut buffer = [ 0 ; 1024 ] ;
1129+ let bytes_read = stream. read ( & mut buffer) . unwrap ( ) ;
1130+ assert ! ( bytes_read > 0 , "HTTP request ended before its headers" ) ;
1131+ request. extend_from_slice ( & buffer[ ..bytes_read] ) ;
1132+ if let Some ( header_end) = request. windows ( 4 ) . position ( |w| w == b"\r \n \r \n " ) {
1133+ break header_end + 4 ;
1134+ }
1135+ } ;
1136+
1137+ let headers = std:: str:: from_utf8 ( & request[ ..header_end] ) . unwrap ( ) ;
1138+ let path = headers. split_whitespace ( ) . nth ( 1 ) . unwrap ( ) . to_string ( ) ;
1139+ let content_length = headers
1140+ . lines ( )
1141+ . filter_map ( |line| line. split_once ( ':' ) )
1142+ . find ( |( name, _) | name. eq_ignore_ascii_case ( "content-length" ) )
1143+ . map ( |( _, value) | value. trim ( ) . parse :: < usize > ( ) . unwrap ( ) )
1144+ . unwrap_or ( 0 ) ;
1145+ while request. len ( ) < header_end + content_length {
1146+ let mut buffer = [ 0 ; 1024 ] ;
1147+ let bytes_read = stream. read ( & mut buffer) . unwrap ( ) ;
1148+ assert ! ( bytes_read > 0 , "HTTP request ended before its body" ) ;
1149+ request. extend_from_slice ( & buffer[ ..bytes_read] ) ;
1150+ }
1151+
1152+ ( path, request[ header_end..header_end + content_length] . to_vec ( ) )
1153+ }
1154+
1155+ fn write_response ( stream : & mut TcpStream , status : & str , body : Vec < u8 > ) {
1156+ let headers = format ! (
1157+ "HTTP/1.1 {status}\r \n content-length: {}\r \n vss-protocol-version: 0\r \n connection: close\r \n \r \n " ,
1158+ body. len( )
1159+ ) ;
1160+ stream. write_all ( headers. as_bytes ( ) ) . unwrap ( ) ;
1161+ stream. write_all ( & body) . unwrap ( ) ;
1162+ }
1163+
1164+ fn serve_empty_vss ( listener : TcpListener ) -> Vec < PutObjectRequest > {
1165+ let mut put_requests = Vec :: new ( ) ;
1166+ while put_requests. len ( ) < 2 {
1167+ let ( mut stream, _) = listener. accept ( ) . unwrap ( ) ;
1168+ let ( path, body) = read_request ( & mut stream) ;
1169+ match path. as_str ( ) {
1170+ "/getObject" => {
1171+ let response = ErrorResponse {
1172+ error_code : ErrorCode :: NoSuchKeyException . into ( ) ,
1173+ message : "missing" . to_string ( ) ,
1174+ } ;
1175+ write_response ( & mut stream, "404 Not Found" , response. encode_to_vec ( ) ) ;
1176+ } ,
1177+ "/listKeyVersions" => write_response (
1178+ & mut stream,
1179+ "200 OK" ,
1180+ ListKeyVersionsResponse :: default ( ) . encode_to_vec ( ) ,
1181+ ) ,
1182+ "/putObjects" => {
1183+ put_requests. push ( PutObjectRequest :: decode ( & body[ ..] ) . unwrap ( ) ) ;
1184+ write_response (
1185+ & mut stream,
1186+ "200 OK" ,
1187+ PutObjectResponse :: default ( ) . encode_to_vec ( ) ,
1188+ ) ;
1189+ } ,
1190+ _ => panic ! ( "unexpected VSS endpoint: {path}" ) ,
1191+ }
1192+ }
1193+ put_requests
1194+ }
1195+
1196+ fn nonce ( request : & PutObjectRequest ) -> Vec < u8 > {
1197+ let storable = Storable :: decode ( & request. transaction_items [ 0 ] . value [ ..] ) . unwrap ( ) ;
1198+ storable. encryption_metadata . unwrap ( ) . nonce
1199+ }
1200+
1201+ #[ tokio:: test]
1202+ async fn schema_and_main_writes_use_unique_nonces ( ) {
1203+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
1204+ let base_url = format ! ( "http://{}" , listener. local_addr( ) . unwrap( ) ) ;
1205+ let server = std:: thread:: spawn ( move || serve_empty_vss ( listener) ) ;
1206+
1207+ let store = VssStore :: new (
1208+ base_url,
1209+ "store" . to_string ( ) ,
1210+ [ 42 ; 32 ] ,
1211+ Arc :: new ( FixedHeaders :: new ( HashMap :: new ( ) ) ) ,
1212+ )
1213+ . unwrap ( ) ;
1214+ KVStore :: write ( & store, "namespace" , "" , "key" , vec ! [ 42 ] ) . await . unwrap ( ) ;
1215+
1216+ let put_requests = server. join ( ) . unwrap ( ) ;
1217+ assert_ne ! (
1218+ nonce( & put_requests[ 0 ] ) ,
1219+ nonce( & put_requests[ 1 ] ) ,
1220+ "schema setup and the first store write reused an encryption nonce"
1221+ ) ;
1222+ }
1223+ }
1224+
11141225#[ cfg( test) ]
11151226#[ cfg( vss_test) ]
11161227mod tests {
0 commit comments