1
1
import { MongoshInvalidInputError } from '@mongosh/errors' ;
2
- import { bson , ClientEncryption as FLEClientEncryption , ServiceProvider } from '@mongosh/service-provider-core' ;
2
+ import { bson , ClientEncryption as FLEClientEncryption , ClientEncryptionTlsOptions , ServiceProvider } from '@mongosh/service-provider-core' ;
3
3
import { expect } from 'chai' ;
4
4
import { EventEmitter } from 'events' ;
5
+ import { promises as fs } from 'fs' ;
6
+ import path from 'path' ;
7
+ import { Duplex } from 'stream' ;
5
8
import sinon , { StubbedInstance , stubInterface } from 'ts-sinon' ;
6
9
import Database from './database' ;
7
10
import { signatures , toShellResult } from './decorators' ;
@@ -47,6 +50,10 @@ const ALGO = 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic';
47
50
48
51
const RAW_CLIENT = { client : 1 } as any ;
49
52
53
+ function getCertPath ( filename : string ) : string {
54
+ return path . join ( __dirname , '..' , '..' , '..' , 'testing' , 'certificates' , filename ) ;
55
+ }
56
+
50
57
describe ( 'Field Level Encryption' , ( ) => {
51
58
let sp : StubbedInstance < ServiceProvider > ;
52
59
let mongo : Mongo ;
@@ -482,6 +489,17 @@ describe('Field Level Encryption', () => {
482
489
483
490
connections = [ ] ;
484
491
sinon . replace ( require ( 'tls' ) , 'connect' , sinon . fake ( ( options , onConnect ) => {
492
+ if ( options . host === 'kmip.example.com' ) {
493
+ // KMIP is not http(s)-based, we don't implement strong fakes for it
494
+ // and instead only verify that a connection has occurred.
495
+ connections . push ( { options } ) ;
496
+ process . nextTick ( onConnect ) ;
497
+ const conn = new Duplex ( {
498
+ read ( ) { setImmediate ( ( ) => this . destroy ( new Error ( 'mock connection broken' ) ) ) ; } ,
499
+ write ( chunk , enc , cb ) { cb ( ) ; }
500
+ } ) ;
501
+ return conn ;
502
+ }
485
503
if ( ! fakeAWSHandlers . some ( handler => handler . host . test ( options . host ) ) ) {
486
504
throw new Error ( `Unexpected TLS connection to ${ options . host } ` ) ;
487
505
}
@@ -498,7 +516,7 @@ describe('Field Level Encryption', () => {
498
516
sinon . restore ( ) ;
499
517
} ) ;
500
518
501
- const kms : [ keyof KMSProvider , KMSProvider [ keyof KMSProvider ] ] [ ] = [
519
+ const kms : [ keyof KMSProvider , KMSProvider [ keyof KMSProvider ] & { tlsOptions ?: ClientEncryptionTlsOptions } ] [ ] = [
502
520
[ 'local' , {
503
521
key : new bson . Binary ( Buffer . from ( 'kh4Gv2N8qopZQMQYMEtww/AkPsIrXNmEMxTrs3tUoTQZbZu4msdRUaR8U5fXD7A7QXYHcEvuu4WctJLoT+NvvV3eeIg3MD+K8H9SR794m/safgRHdIfy6PD+rFpvmFbY' , 'base64' ) , 0 )
504
522
} ] ,
@@ -528,15 +546,25 @@ lt6waE7I2uSPqIC20LcCIQDJQYIHQII+3YaPqyhGgqMexuuuGx+lDKD6/Fu/JwPb
528
546
5QIhAKthiYcYKlL9h8bjDsQhZDUACPasjzdsDEdq8inDyLOFAiEAmCr/tZwA3qeA
529
547
ZoBzI10DGPIuoKXBd3nk/eBxPkaxlEECIQCNymjsoI7GldtujVnr1qT+3yedLfHK
530
548
srDVjIT3LsvTqw==`
549
+ } ] ,
550
+ [ 'kmip' , {
551
+ endpoint : 'kmip.example.com:123' ,
552
+ tlsOptions : {
553
+ tlsCertificateKeyFile : getCertPath ( 'client.bundle.encrypted.pem' ) ,
554
+ tlsCertificateKeyFilePassword : 'p4ssw0rd' ,
555
+ tlsCAFile : getCertPath ( 'ca.crt' )
556
+ }
531
557
} ]
532
558
] ;
533
- for ( const [ kmsName , kmsOptions ] of kms ) {
559
+ for ( const [ kmsName , kmsAndTlsOptions ] of kms ) {
534
560
// eslint-disable-next-line no-loop-func
535
561
it ( `provides ClientEncryption for kms=${ kmsName } ` , async ( ) => {
562
+ const kmsOptions = { ...kmsAndTlsOptions , tlsOptions : undefined } ;
536
563
const mongo = new Mongo ( instanceState , uri , {
537
564
keyVaultNamespace : `${ dbname } .__keyVault` ,
538
565
kmsProviders : { [ kmsName ] : kmsOptions } as any ,
539
- explicitEncryptionOnly : true
566
+ explicitEncryptionOnly : true ,
567
+ tlsOptions : { [ kmsName ] : kmsAndTlsOptions . tlsOptions ?? undefined }
540
568
} , serviceProvider ) ;
541
569
await mongo . connect ( ) ;
542
570
instanceState . mongos . push ( mongo ) ;
@@ -567,6 +595,28 @@ srDVjIT3LsvTqw==`
567
595
keyName : 'foobar'
568
596
} ) ;
569
597
break ;
598
+ case 'kmip' :
599
+ try {
600
+ await keyVault . createKey ( 'kmip' , undefined ) ;
601
+ } catch ( err ) {
602
+ // See above, we don't attempt to successfully encrypt/decrypt
603
+ // when using KMIP
604
+ expect ( err . message ) . to . include ( 'KMS request failed' ) ;
605
+ expect ( connections ) . to . deep . equal ( [ {
606
+ options : {
607
+ host : 'kmip.example.com' ,
608
+ servername : 'kmip.example.com' ,
609
+ port : 123 ,
610
+ passphrase : 'p4ssw0rd' ,
611
+ ca : await fs . readFile ( getCertPath ( 'ca.crt' ) ) ,
612
+ cert : await fs . readFile ( getCertPath ( 'client.bundle.encrypted.pem' ) ) ,
613
+ key : await fs . readFile ( getCertPath ( 'client.bundle.encrypted.pem' ) )
614
+ }
615
+ } ] ) ;
616
+ return ;
617
+ }
618
+ expect . fail ( 'missed exception' ) ;
619
+ break ;
570
620
default :
571
621
throw new Error ( `unreachable ${ kmsName } ` ) ;
572
622
}
@@ -584,12 +634,12 @@ srDVjIT3LsvTqw==`
584
634
expect ( encrypted . sub_type ) . to . equal ( 6 ) ; // Encrypted
585
635
expect ( decrypted ) . to . deep . equal ( plaintextValue ) ;
586
636
587
- if ( ( kmsOptions as any ) . sessionToken ) { // as any -> NODE-3107
637
+ if ( 'sessionToken' in kmsOptions ) {
588
638
expect (
589
639
connections . map (
590
640
conn => conn . requests . map (
591
641
req => req . headers [ 'x-amz-security-token' ] ) ) . flat ( ) )
592
- . to . include ( ( kmsOptions as any ) . sessionToken ) ;
642
+ . to . include ( kmsOptions . sessionToken ) ;
593
643
}
594
644
} ) ;
595
645
}
0 commit comments