@@ -9,7 +9,7 @@ use cipherstash_client::{
9
9
ScopedCipher , SteVec , TypeParseError ,
10
10
} ,
11
11
schema:: ColumnConfig ,
12
- zerokms:: { self , encrypted_record , EncryptedRecord , WithContext , ZeroKMSWithClientKey } ,
12
+ zerokms:: { self , EncryptedRecord , WithContext , ZeroKMSWithClientKey } ,
13
13
} ;
14
14
use encrypt_config:: { EncryptConfig , Identifier } ;
15
15
use neon:: prelude:: * ;
@@ -43,8 +43,8 @@ impl Finalize for Client {}
43
43
pub enum Encrypted {
44
44
#[ serde( rename = "ct" ) ]
45
45
Ciphertext {
46
- #[ serde( rename = "c" , with = "encrypted_record::formats::mp_base85" ) ]
47
- ciphertext : EncryptedRecord ,
46
+ #[ serde( rename = "c" ) ]
47
+ ciphertext : String ,
48
48
#[ serde( rename = "o" ) ]
49
49
ore_index : Option < Vec < String > > ,
50
50
#[ serde( rename = "m" ) ]
@@ -139,11 +139,12 @@ async fn new_client_inner(encrypt_config: EncryptConfig) -> Result<Client, Error
139
139
140
140
fn encrypt ( mut cx : FunctionContext ) -> JsResult < JsPromise > {
141
141
let client = ( * * cx. argument :: < JsBox < Client > > ( 0 ) ?) . clone ( ) ;
142
- let plaintext = cx. argument :: < JsString > ( 1 ) ?. value ( & mut cx) ;
143
- let column_name = cx. argument :: < JsString > ( 2 ) ?. value ( & mut cx) ;
144
- let table_name = cx. argument :: < JsString > ( 3 ) ?. value ( & mut cx) ;
145
- let lock_context = encryption_context_from_js_value ( cx. argument_opt ( 4 ) , & mut cx) ?;
146
- let service_token = service_token_from_js_value ( cx. argument_opt ( 5 ) , & mut cx) ?;
142
+ let ( plaintext_target, ident) = plaintext_target_from_js_object (
143
+ cx. argument :: < JsObject > ( 1 ) ?,
144
+ & client. encrypt_config ,
145
+ & mut cx,
146
+ ) ?;
147
+ let service_token = service_token_from_js_value ( cx. argument_opt ( 2 ) , & mut cx) ?;
147
148
148
149
let rt = runtime ( & mut cx) ?;
149
150
let channel = cx. channel ( ) ;
@@ -159,15 +160,7 @@ fn encrypt(mut cx: FunctionContext) -> JsResult<JsPromise> {
159
160
//
160
161
// This task will _not_ block the JavaScript main thread.
161
162
rt. spawn ( async move {
162
- let ciphertext_result = encrypt_inner (
163
- client,
164
- plaintext,
165
- column_name,
166
- table_name,
167
- lock_context,
168
- service_token,
169
- )
170
- . await ;
163
+ let ciphertext_result = encrypt_inner ( client, plaintext_target, ident, service_token) . await ;
171
164
172
165
// Settle the promise from the result of a closure. JavaScript exceptions
173
166
// will be converted to a Promise rejection.
@@ -177,8 +170,7 @@ fn encrypt(mut cx: FunctionContext) -> JsResult<JsPromise> {
177
170
// should be performed outside of it.
178
171
deferred. settle_with ( & channel, move |mut cx| {
179
172
let ciphertext = ciphertext_result. or_else ( |err| cx. throw_error ( err. to_string ( ) ) ) ?;
180
-
181
- Ok ( cx. string ( ciphertext) )
173
+ eql_encrypted_to_js ( ciphertext, & mut cx)
182
174
} ) ;
183
175
} ) ;
184
176
@@ -187,24 +179,13 @@ fn encrypt(mut cx: FunctionContext) -> JsResult<JsPromise> {
187
179
188
180
async fn encrypt_inner (
189
181
client : Client ,
190
- plaintext : String ,
191
- column_name : String ,
192
- table_name : String ,
193
- encryption_context : Vec < zerokms:: Context > ,
182
+ plaintext_target : PlaintextTarget ,
183
+ ident : Identifier ,
194
184
service_token : Option < ServiceToken > ,
195
- ) -> Result < String , Error > {
196
- let ident = Identifier :: new ( table_name, column_name) ;
197
-
198
- let column_config = client
199
- . encrypt_config
200
- . get ( & ident)
201
- . ok_or_else ( || Error :: UnknownColumn ( ident. clone ( ) ) ) ?;
202
-
185
+ ) -> Result < Encrypted , Error > {
203
186
let mut pipeline = ReferencedPendingPipeline :: new ( client. cipher ) ;
204
- let mut encryptable = PlaintextTarget :: new ( plaintext, column_config. clone ( ) ) ;
205
- encryptable. context = encryption_context;
206
187
207
- pipeline. add_with_ref :: < PlaintextTarget > ( encryptable , 0 ) ?;
188
+ pipeline. add_with_ref :: < PlaintextTarget > ( plaintext_target , 0 ) ?;
208
189
209
190
let mut source_encrypted = pipeline. encrypt ( service_token) . await ?;
210
191
@@ -214,9 +195,7 @@ async fn encrypt_inner(
214
195
)
215
196
} ) ?;
216
197
217
- let eql_payload = to_eql_encrypted ( encrypted, & ident) ?;
218
-
219
- eql_encrypted_to_json_string ( & eql_payload)
198
+ to_eql_encrypted ( encrypted, & ident)
220
199
}
221
200
222
201
fn encrypt_bulk ( mut cx : FunctionContext ) -> JsResult < JsPromise > {
@@ -238,7 +217,7 @@ fn encrypt_bulk(mut cx: FunctionContext) -> JsResult<JsPromise> {
238
217
239
218
deferred. settle_with ( & channel, move |mut cx| {
240
219
let ciphertexts = ciphertexts_result. or_else ( |err| cx. throw_error ( err. to_string ( ) ) ) ?;
241
- js_array_from_string_vec ( ciphertexts, & mut cx)
220
+ js_array_from_eql_encrypted_vec ( ciphertexts, & mut cx)
242
221
} ) ;
243
222
} ) ;
244
223
@@ -249,7 +228,7 @@ async fn encrypt_bulk_inner(
249
228
client : Client ,
250
229
plaintext_targets : Vec < ( PlaintextTarget , Identifier ) > ,
251
230
service_token : Option < ServiceToken > ,
252
- ) -> Result < Vec < String > , Error > {
231
+ ) -> Result < Vec < Encrypted > , Error > {
253
232
let len = plaintext_targets. len ( ) ;
254
233
let mut pipeline = ReferencedPendingPipeline :: new ( client. cipher ) ;
255
234
let ( plaintext_targets, identifiers) : ( Vec < PlaintextTarget > , Vec < Identifier > ) =
@@ -261,7 +240,7 @@ async fn encrypt_bulk_inner(
261
240
262
241
let mut source_encrypted = pipeline. encrypt ( service_token) . await ?;
263
242
264
- let mut results: Vec < String > = Vec :: with_capacity ( len) ;
243
+ let mut results: Vec < Encrypted > = Vec :: with_capacity ( len) ;
265
244
266
245
for i in 0 ..len {
267
246
let encrypted = source_encrypted. remove ( i) . ok_or_else ( || {
@@ -278,7 +257,7 @@ async fn encrypt_bulk_inner(
278
257
279
258
let eql_payload = to_eql_encrypted ( encrypted, ident) ?;
280
259
281
- results. push ( eql_encrypted_to_json_string ( & eql_payload) ? ) ;
260
+ results. push ( eql_payload) ;
282
261
}
283
262
284
263
Ok ( results)
@@ -404,18 +383,19 @@ fn service_token_from_js_value(
404
383
value : Option < Handle < JsValue > > ,
405
384
cx : & mut FunctionContext ,
406
385
) -> NeonResult < Option < ServiceToken > > {
407
- if let Some ( service_token) = value {
408
- let service_token: Handle < JsObject > = service_token. downcast_or_throw ( cx) ?;
386
+ match value {
387
+ Some ( service_token) if is_defined ( service_token, cx) => {
388
+ let service_token: Handle < JsObject > = service_token. downcast_or_throw ( cx) ?;
409
389
410
- let token = service_token
411
- . get :: < JsString , _ , _ > ( cx, "accessToken" ) ?
412
- . value ( cx) ;
390
+ let token = service_token
391
+ . get :: < JsString , _ , _ > ( cx, "accessToken" ) ?
392
+ . value ( cx) ;
413
393
414
- let expiry = service_token. get :: < JsNumber , _ , _ > ( cx, "expiry" ) ?. value ( cx) ;
394
+ let expiry = service_token. get :: < JsNumber , _ , _ > ( cx, "expiry" ) ?. value ( cx) ;
415
395
416
- Ok ( Some ( ServiceToken :: new ( token, expiry as u64 ) ) )
417
- } else {
418
- Ok ( None )
396
+ Ok ( Some ( ServiceToken :: new ( token, expiry as u64 ) ) )
397
+ }
398
+ _ => Ok ( None ) ,
419
399
}
420
400
}
421
401
@@ -430,29 +410,38 @@ fn plaintext_targets_from_js_array(
430
410
431
411
for js_value in js_values {
432
412
let obj: Handle < JsObject > = js_value. downcast_or_throw ( cx) ?;
413
+ let ( plaintext_target, ident) = plaintext_target_from_js_object ( obj, & encrypt_config, cx) ?;
414
+
415
+ plaintext_targets. push ( ( plaintext_target, ident) ) ;
416
+ }
433
417
434
- let plaintext = obj. get :: < JsString , _ , _ > ( cx, "plaintext" ) ?. value ( cx) ;
418
+ Ok ( plaintext_targets)
419
+ }
435
420
436
- let column = obj. get :: < JsString , _ , _ > ( cx, "column" ) ?. value ( cx) ;
437
- let table = obj. get :: < JsString , _ , _ > ( cx, "table" ) ?. value ( cx) ;
421
+ fn plaintext_target_from_js_object (
422
+ value : Handle < ' _ , JsObject > ,
423
+ encrypt_config : & Arc < HashMap < Identifier , ColumnConfig > > ,
424
+ cx : & mut FunctionContext ,
425
+ ) -> NeonResult < ( PlaintextTarget , Identifier ) > {
426
+ let plaintext = value. get :: < JsString , _ , _ > ( cx, "plaintext" ) ?. value ( cx) ;
438
427
439
- let lock_context = obj . get_opt :: < JsValue , _ , _ > ( cx, "lockContext " ) ?;
440
- let lock_context = encryption_context_from_js_value ( lock_context , cx ) ? ;
428
+ let column = value . get :: < JsString , _ , _ > ( cx, "column " ) ?. value ( cx ) ;
429
+ let table = value . get :: < JsString , _ , _ > ( cx , "table" ) ? . value ( cx ) ;
441
430
442
- let ident = Identifier :: new ( table, column) ;
431
+ let lock_context = value. get_opt :: < JsValue , _ , _ > ( cx, "lockContext" ) ?;
432
+ let lock_context = encryption_context_from_js_value ( lock_context, cx) ?;
443
433
444
- let column_config = encrypt_config
445
- . get ( & ident)
446
- . ok_or_else ( || Error :: UnknownColumn ( ident. clone ( ) ) )
447
- . or_else ( |err| cx. throw_error ( err. to_string ( ) ) ) ?;
434
+ let ident = Identifier :: new ( table, column) ;
448
435
449
- let mut plaintext_target = PlaintextTarget :: new ( plaintext, column_config. clone ( ) ) ;
450
- plaintext_target. context = lock_context;
436
+ let column_config = encrypt_config
437
+ . get ( & ident)
438
+ . ok_or_else ( || Error :: UnknownColumn ( ident. clone ( ) ) )
439
+ . or_else ( |err| cx. throw_error ( err. to_string ( ) ) ) ?;
451
440
452
- plaintext_targets . push ( ( plaintext_target , ident ) ) ;
453
- }
441
+ let mut plaintext_target = PlaintextTarget :: new ( plaintext , column_config . clone ( ) ) ;
442
+ plaintext_target . context = lock_context ;
454
443
455
- Ok ( plaintext_targets )
444
+ Ok ( ( plaintext_target , ident ) )
456
445
}
457
446
458
447
fn ciphertexts_from_js_array (
@@ -490,6 +479,34 @@ fn js_array_from_string_vec<'a, C: Context<'a>>(
490
479
Ok ( js_array)
491
480
}
492
481
482
+ fn js_array_from_u16_vec < ' a , C : Context < ' a > > (
483
+ vec : Vec < u16 > ,
484
+ cx : & mut C ,
485
+ ) -> NeonResult < Handle < ' a , JsArray > > {
486
+ let js_array = JsArray :: new ( cx, vec. len ( ) ) ;
487
+
488
+ for ( i, value) in vec. iter ( ) . enumerate ( ) {
489
+ let js_number = cx. number ( * value) ;
490
+ js_array. set ( cx, i as u32 , js_number) ?;
491
+ }
492
+
493
+ Ok ( js_array)
494
+ }
495
+
496
+ fn js_array_from_eql_encrypted_vec < ' a , C : Context < ' a > > (
497
+ vec : Vec < Encrypted > ,
498
+ cx : & mut C ,
499
+ ) -> NeonResult < Handle < ' a , JsArray > > {
500
+ let js_array = JsArray :: new ( cx, vec. len ( ) ) ;
501
+
502
+ for ( i, value) in vec. into_iter ( ) . enumerate ( ) {
503
+ let js_obj = eql_encrypted_to_js ( value, cx) ?;
504
+ js_array. set ( cx, i as u32 , js_obj) ?;
505
+ }
506
+
507
+ Ok ( js_array)
508
+ }
509
+
493
510
fn encrypted_record_from_mp_base85 (
494
511
base85str : & str ,
495
512
encryption_context : Vec < zerokms:: Context > ,
@@ -554,6 +571,12 @@ fn to_eql_encrypted(
554
571
} ;
555
572
}
556
573
574
+ let ciphertext = ciphertext
575
+ . to_mp_base85 ( )
576
+ // The error type from `to_mp_base85` isn't public, so we don't derive an error for this one.
577
+ // Instead, we use `map_err`.
578
+ . map_err ( |err| Error :: Base85 ( err. to_string ( ) ) ) ?;
579
+
557
580
Ok ( Encrypted :: Ciphertext {
558
581
ciphertext,
559
582
identifier : identifier. to_owned ( ) ,
@@ -571,6 +594,71 @@ fn to_eql_encrypted(
571
594
}
572
595
}
573
596
597
+ fn eql_encrypted_to_js < ' cx , C : Context < ' cx > > (
598
+ encrypted : Encrypted ,
599
+ cx : & mut C ,
600
+ ) -> NeonResult < Handle < ' cx , JsObject > > {
601
+ let obj: Handle < JsObject > = cx. empty_object ( ) ;
602
+
603
+ let Encrypted :: Ciphertext {
604
+ ciphertext,
605
+ ore_index,
606
+ match_index,
607
+ unique_index,
608
+ identifier,
609
+ version,
610
+ } = encrypted
611
+ else {
612
+ return cx
613
+ . throw_error ( Error :: Unimplemented ( "encrypted JSON columns" . to_string ( ) ) . to_string ( ) ) ;
614
+ } ;
615
+
616
+ let k = cx. string ( "ct" ) ;
617
+ obj. set ( cx, "k" , k) ?;
618
+
619
+ let c = cx. string ( ciphertext) ;
620
+ obj. set ( cx, "c" , c) ?;
621
+
622
+ if let Some ( ore_index) = ore_index {
623
+ let o = js_array_from_string_vec ( ore_index, cx) ?;
624
+ obj. set ( cx, "o" , o) ?;
625
+ } else {
626
+ let o = cx. null ( ) ;
627
+ obj. set ( cx, "o" , o) ?;
628
+ }
629
+
630
+ if let Some ( match_index) = match_index {
631
+ let m = js_array_from_u16_vec ( match_index, cx) ?;
632
+ obj. set ( cx, "m" , m) ?;
633
+ } else {
634
+ let m = cx. null ( ) ;
635
+ obj. set ( cx, "m" , m) ?;
636
+ }
637
+
638
+ if let Some ( unique_index) = unique_index {
639
+ let u = cx. string ( unique_index) ;
640
+ obj. set ( cx, "u" , u) ?;
641
+ } else {
642
+ let u = cx. null ( ) ;
643
+ obj. set ( cx, "u" , u) ?;
644
+ }
645
+
646
+ let i = cx. empty_object ( ) ;
647
+
648
+ let col = cx. string ( identifier. column ) ;
649
+ i. set ( cx, "c" , col) ?;
650
+
651
+ let t = cx. string ( identifier. table ) ;
652
+ i. set ( cx, "t" , t) ?;
653
+
654
+ obj. set ( cx, "i" , i) ?;
655
+
656
+ let v = cx. number ( version) ;
657
+ obj. set ( cx, "v" , v) ?;
658
+
659
+ Ok ( obj)
660
+ }
661
+
574
662
fn format_index_term_binary ( bytes : & Vec < u8 > ) -> String {
575
663
hex:: encode ( bytes)
576
664
}
@@ -596,13 +684,8 @@ fn format_index_term_ore(bytes: &Vec<u8>) -> Vec<String> {
596
684
vec ! [ format_index_term_ore_bytea( bytes) ]
597
685
}
598
686
599
- fn eql_encrypted_to_json_string ( encrypted : & Encrypted ) -> Result < String , Error > {
600
- serde_json:: to_string ( encrypted) . map_err ( |_| {
601
- Error :: InvariantViolation (
602
- "expected EQL payload to be serialiable as JSON, but it could not be serialized"
603
- . to_string ( ) ,
604
- )
605
- } )
687
+ fn is_defined ( js_value : Handle < ' _ , JsValue > , cx : & mut FunctionContext ) -> bool {
688
+ !js_value. is_a :: < JsUndefined , _ > ( cx)
606
689
}
607
690
608
691
#[ neon:: main]
0 commit comments