├── .gitignore ├── .kmip ├── README.md ├── certs │ ├── ca.pem │ ├── client.pem │ └── server.pem └── kmip_server │ ├── .gitignore │ ├── activate_venv.sh │ ├── kms_kmip_server.py │ ├── pykmip.conf │ └── pykmip.db ├── README.md ├── dotnet ├── .gitignore ├── CSFLE.sln ├── CSFLE │ ├── AutoEncryptionHelper.cs │ ├── CSFLE.csproj │ ├── JsonSchemaCreator.cs │ ├── KmsKeyHelper.cs │ ├── Program.cs │ └── master-key.txt └── README.md ├── go ├── .gitignore ├── README.md ├── csfle │ ├── data_key.go │ └── encrypted_client.go ├── go.mod ├── go.sum ├── kms │ └── provider.go ├── main.go ├── master-key.txt ├── patient │ └── patient.go └── schema │ └── json_schema.go ├── java ├── .envrc ├── .gitignore ├── README.md ├── build.gradle ├── configure_certs.sh ├── master-key.txt ├── maven.config.tmpl ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── mongodb │ └── csfle │ ├── DataEncryptionKeyCreator.java │ ├── InsertDataWithEncryptedFields.java │ └── util │ └── CSFLEHelpers.java ├── nodejs ├── .eslintrc.js ├── README.md ├── clients.js ├── helpers.js ├── kms.js ├── make-data-key.js ├── master-key.txt ├── package-lock.json └── package.json └── python ├── README.md ├── app.py ├── helpers.py ├── make_data_key.py ├── master-key.txt └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | node_modules/ 3 | __pycache__/ 4 | java/bin/ 5 | java/target/ 6 | **/mongocryptd.pid 7 | .classpath 8 | .project 9 | *.pyc 10 | .env 11 | -------------------------------------------------------------------------------- /.kmip/README.md: -------------------------------------------------------------------------------- 1 | # KMIP Test Resources 2 | 3 | This directory contains resources used to test a KMIP compliant KMS for use in CSFLE. 4 | 5 | ## KMIP Server 6 | 7 | The `kmip_server` directory contains a KMIP server for 8 | testing. This KMIP server is unstable. Do not use this KMIP 9 | server in a production environment. 10 | 11 | ## Certificates 12 | 13 | The `certs` directory contains certificates used to establish 14 | a connection to the KMIP test server. Do not use these 15 | certificates in a production environment. 16 | -------------------------------------------------------------------------------- /.kmip/certs/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDfzCCAmegAwIBAgIDB1MGMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy 3 | aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u 4 | Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx 5 | CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIwMjMxMVoXDTM5MDUyMjIwMjMxMVoweTEb 6 | MBkGA1UEAxMSRHJpdmVycyBUZXN0aW5nIENBMRAwDgYDVQQLEwdEcml2ZXJzMRAw 7 | DgYDVQQKEwdNb25nb0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQI 8 | EwhOZXcgWW9yazELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 9 | ggEKAoIBAQCl7VN+WsQfHlwapcOpTLZVoeMAl1LTbWTFuXSAavIyy0W1Ytky1UP/ 10 | bxCSW0mSWwCgqoJ5aXbAvrNRp6ArWu3LsTQIEcD3pEdrFIVQhYzWUs9fXqPyI9k+ 11 | QNNQ+MRFKeGteTPYwF2eVEtPzUHU5ws3+OKp1m6MCLkwAG3RBFUAfddUnLvGoZiT 12 | pd8/eNabhgHvdrCw+tYFCWvSjz7SluEVievpQehrSEPKe8DxJq/IM3tSl3tdylzT 13 | zeiKNO7c7LuQrgjAfrZl7n2SriHIlNmqiDR/kdd8+TxBuxjFlcf2WyHCO3lIcIgH 14 | KXTlhUCg50KfHaxHu05Qw0x8869yIzqbAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8w 15 | DQYJKoZIhvcNAQELBQADggEBAEHuhTL8KQZcKCTSJbYA9MgZj7U32arMGBbc1hiq 16 | VBREwvdVz4+9tIyWMzN9R/YCKmUTnCq8z3wTlC8kBtxYn/l4Tj8nJYcgLJjQ0Fwe 17 | gT564CmvkUat8uXPz6olOCdwkMpJ9Sj62i0mpgXJdBfxKQ6TZ9yGz6m3jannjZpN 18 | LchB7xSAEWtqUgvNusq0dApJsf4n7jZ+oBZVaQw2+tzaMfaLqHgMwcu1FzA8UKCD 19 | sxCgIsZUs8DdxaD418Ot6nPfheOTqe24n+TTa+Z6O0W0QtnofJBx7tmAo1aEc57i 20 | 77s89pfwIJetpIlhzNSMKurCAocFCJMJLAASJFuu6dyDvPo= 21 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /.kmip/certs/client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAsNS8UEuin7/K29jXfIOLpIoh1jEyWVqxiie2Onx7uJJKcoKo 3 | khA3XeUnVN0k6X5MwYWcN52xcns7LYtyt06nRpTG2/emoV44w9uKTuHsvUbiOwSV 4 | m/ToKQQ4FUFZoqorXH+ZmJuIpJNfoW+3CkE1vEDCIecIq6BNg5ySsPtvSuSJHGjp 5 | mc7/5ZUDvFE2aJ8QbJU3Ws0HXiEb6ymi048LlzEL2VKX3w6mqqh+7dcZGAy7qYk2 6 | 5FZ9ktKvCeQau7mTyU1hsPrKFiKtMN8Q2ZAItX13asw5/IeSTq2LgLFHlbj5Kpq4 7 | GmLdNCshzH5X7Ew3IYM8EHmsX8dmD6mhv7vpVwIDAQABAoIBABOdpb4qhcG+3twA 8 | c/cGCKmaASLnljQ/UU6IFTjrsjXJVKTbRaPeVKX/05sgZQXZ0t3s2mV5AsQ2U1w8 9 | Cd+3w+qaemzQThW8hAOGCROzEDX29QWi/o2sX0ydgTMqaq0Wv3SlWv6I0mGfT45y 10 | /BURIsrdTCvCmz2erLqa1dL4MWJXRFjT9UTs5twlecIOM2IHKoGGagFhymRK4kDe 11 | wTRC9fpfoAgyfus3pCO/wi/F8yKGPDEwY+zgkhrJQ+kSeki7oKdGD1H540vB8gRt 12 | EIqssE0Y6rEYf97WssQlxJgvoJBDSftOijS6mwvoasDUwfFqyyPiirawXWWhHXkc 13 | DjIi/XECgYEA5xfjilw9YyM2UGQNESbNNunPcj7gDZbN347xJwmYmi9AUdPLt9xN 14 | 3XaMqqR22k1DUOxC/5hH0uiXir7mDfqmC+XS/ic/VOsa3CDWejkEnyGLiwSHY502 15 | wD/xWgHwUiGVAG9HY64vnDGm6L3KGXA2oqxanL4V0+0+Ht49pZ16i8sCgYEAw+Ox 16 | CHGtpkzjCP/z8xr+1VTSdpc/4CP2HONnYopcn48KfQnf7Nale69/1kZpypJlvQSG 17 | eeA3jMGigNJEkb8/kaVoRLCisXcwLc0XIfCTeiK6FS0Ka30D/84Qm8UsHxRdpGkM 18 | kYITAa2r64tgRL8as4/ukeXBKE+oOhX43LeEfyUCgYBkf7IX2Ndlhsm3GlvIarxy 19 | NipeP9PGdR/hKlPbq0OvQf9R1q7QrcE7H7Q6/b0mYNV2mtjkOQB7S2WkFDMOP0P5 20 | BqDEoKLdNkV/F9TOYH+PCNKbyYNrodJOt0Ap6Y/u1+Xpw3sjcXwJDFrO+sKqX2+T 21 | PStG4S+y84jBedsLbDoAEwKBgQCTz7/KC11o2yOFqv09N+WKvBKDgeWlD/2qFr3w 22 | UU9K5viXGVhqshz0k5z25vL09Drowf1nAZVpFMO2SPOMtq8VC6b+Dfr1xmYIaXVH 23 | Gu1tf77CM9Zk/VSDNc66e7GrUgbHBK2DLo+A+Ld9aRIfTcSsMbNnS+LQtCrQibvb 24 | cG7+MQKBgQCY11oMT2dUekoZEyW4no7W5D74lR8ztMjp/fWWTDo/AZGPBY6cZoZF 25 | IICrzYtDT/5BzB0Jh1f4O9ZQkm5+OvlFbmoZoSbMzHL3oJCBOY5K0/kdGXL46WWh 26 | IRJSYakNU6VIS7SjDpKgm9D8befQqZeoSggSjIIULIiAtYgS80vmGA== 27 | -----END RSA PRIVATE KEY----- 28 | -----BEGIN CERTIFICATE----- 29 | MIIDgzCCAmugAwIBAgIDAxOUMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy 30 | aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u 31 | Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx 32 | CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIzNTU1NFoXDTM5MDUyMjIzNTU1NFowaTEP 33 | MA0GA1UEAxMGY2xpZW50MRAwDgYDVQQLEwdEcml2ZXJzMQwwCgYDVQQKEwNNREIx 34 | FjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYD 35 | VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALDUvFBLop+/ 36 | ytvY13yDi6SKIdYxMllasYontjp8e7iSSnKCqJIQN13lJ1TdJOl+TMGFnDedsXJ7 37 | Oy2LcrdOp0aUxtv3pqFeOMPbik7h7L1G4jsElZv06CkEOBVBWaKqK1x/mZibiKST 38 | X6FvtwpBNbxAwiHnCKugTYOckrD7b0rkiRxo6ZnO/+WVA7xRNmifEGyVN1rNB14h 39 | G+spotOPC5cxC9lSl98Opqqofu3XGRgMu6mJNuRWfZLSrwnkGru5k8lNYbD6yhYi 40 | rTDfENmQCLV9d2rMOfyHkk6ti4CxR5W4+SqauBpi3TQrIcx+V+xMNyGDPBB5rF/H 41 | Zg+pob+76VcCAwEAAaMkMCIwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF 42 | BwMCMA0GCSqGSIb3DQEBCwUAA4IBAQAqRcLAGvYMaGYOV4HJTzNotT2qE0I9THNQ 43 | wOV1fBg69x6SrUQTQLjJEptpOA288Wue6Jt3H+p5qAGV5GbXjzN/yjCoItggSKxG 44 | Xg7279nz6/C5faoIKRjpS9R+MsJGlttP9nUzdSxrHvvqm62OuSVFjjETxD39DupE 45 | YPFQoHOxdFTtBQlc/zIKxVdd20rs1xJeeU2/L7jtRBSPuR/Sk8zot7G2/dQHX49y 46 | kHrq8qz12kj1T6XDXf8KZawFywXaz0/Ur+fUYKmkVk1T0JZaNtF4sKqDeNE4zcns 47 | p3xLVDSl1Q5Gwj7bgph9o4Hxs9izPwiqjmNaSjPimGYZ399zcurY 48 | -----END CERTIFICATE----- 49 | -------------------------------------------------------------------------------- /.kmip/certs/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAhNrB0E6GY/kFSd8/vNpu/t952tbnOsD5drV0XPvmuy7SgKDY 3 | a/S+xb/jPnlZKKehdBnH7qP/gYbv34ZykzcDFZscjPLiGc2cRGP+NQCSFK0d2/7d 4 | y15zSD3zhj14G8+MkpAejTU+0/qFNZMc5neDvGanTe0+8aWa0DXssM0MuTxIv7j6 5 | CtsMWeqLLofN7a1Kw2UvmieCHfHMuA/08pJwRnV/+5T9WONBPJja2ZQRrG1BjpI4 6 | 81zSPUZesIqi8yDlExdvgNaRZIEHi/njREqwVgJOZomUY57zmKypiMzbz48dDTsV 7 | gUStxrEqbaP+BEjQYPX5+QQk4GdMjkLf52LR6QIDAQABAoIBAHSs+hHLJNOf2zkp 8 | S3y8CUblVMsQeTpsR6otaehPgi9Zy50TpX4KD5D0GMrBH8BIl86y5Zd7h+VlcDzK 9 | gs0vPxI2izhuBovKuzaE6rf5rFFkSBjxGDCG3o/PeJOoYFdsS3RcBbjVzju0hFCs 10 | xnDQ/Wz0anJRrTnjyraY5SnQqx/xuhLXkj/lwWoWjP2bUqDprnuLOj16soNu60Um 11 | JziWbmWx9ty0wohkI/8DPBl9FjSniEEUi9pnZXPElFN6kwPkgdfT5rY/TkMH4lsu 12 | ozOUc5xgwlkT6kVjXHcs3fleuT/mOfVXLPgNms85JKLucfd6KiV7jYZkT/bXIjQ+ 13 | 7CZEn0ECgYEA5QiKZgsfJjWvZpt21V/i7dPje2xdwHtZ8F9NjX7ZUFA7mUPxUlwe 14 | GiXxmy6RGzNdnLOto4SF0/7ebuF3koO77oLup5a2etL+y/AnNAufbu4S5D72sbiz 15 | wdLzr3d5JQ12xeaEH6kQNk2SD5/ShctdS6GmTgQPiJIgH0MIdi9F3v0CgYEAlH84 16 | hMWcC+5b4hHUEexeNkT8kCXwHVcUjGRaYFdSHgovvWllApZDHSWZ+vRcMBdlhNPu 17 | 09Btxo99cjOZwGYJyt20QQLGc/ZyiOF4ximQzabTeFgLkTH3Ox6Mh2Rx9yIruYoX 18 | nE3UfMDkYELanEJUv0zenKpZHw7tTt5yXXSlEF0CgYBSsEOvVcKYO/eoluZPYQAA 19 | F2jgzZ4HeUFebDoGpM52lZD+463Dq2hezmYtPaG77U6V3bUJ/TWH9VN/Or290vvN 20 | v83ECcC2FWlSXdD5lFyqYx/E8gqE3YdgqfW62uqM+xBvoKsA9zvYLydVpsEN9v8m 21 | 6CSvs/2btA4O21e5u5WBTQKBgGtAb6vFpe0gHRDs24SOeYUs0lWycPhf+qFjobrP 22 | lqnHpa9iPeheat7UV6BfeW3qmBIVl/s4IPE2ld4z0qqZiB0Tf6ssu/TpXNPsNXS6 23 | dLFz+myC+ufFdNEoQUtQitd5wKbjTCZCOGRaVRgJcSdG6Tq55Fa22mOKPm+mTmed 24 | ZdKpAoGAFsTYBAHPxs8nzkCJCl7KLa4/zgbgywO6EcQgA7tfelB8bc8vcAMG5o+8 25 | YqAfwxrzhVSVbJx0fibTARXROmbh2pn010l2wj3+qUajM8NiskCPFbSjGy7HSUze 26 | P8Kt1uMDJdj55gATzn44au31QBioZY2zXleorxF21cr+BZCJgfA= 27 | -----END RSA PRIVATE KEY----- 28 | -----BEGIN CERTIFICATE----- 29 | MIIDlTCCAn2gAwIBAgICdxUwDQYJKoZIhvcNAQELBQAweTEbMBkGA1UEAxMSRHJp 30 | dmVycyBUZXN0aW5nIENBMRAwDgYDVQQLEwdEcml2ZXJzMRAwDgYDVQQKEwdNb25n 31 | b0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQIEwhOZXcgWW9yazEL 32 | MAkGA1UEBhMCVVMwHhcNMTkwNTIyMjIzMjU2WhcNMzkwNTIyMjIzMjU2WjBwMRIw 33 | EAYDVQQDEwlsb2NhbGhvc3QxEDAOBgNVBAsTB0RyaXZlcnMxEDAOBgNVBAoTB01v 34 | bmdvREIxFjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3Jr 35 | MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAITa 36 | wdBOhmP5BUnfP7zabv7fedrW5zrA+Xa1dFz75rsu0oCg2Gv0vsW/4z55WSinoXQZ 37 | x+6j/4GG79+GcpM3AxWbHIzy4hnNnERj/jUAkhStHdv+3ctec0g984Y9eBvPjJKQ 38 | Ho01PtP6hTWTHOZ3g7xmp03tPvGlmtA17LDNDLk8SL+4+grbDFnqiy6Hze2tSsNl 39 | L5ongh3xzLgP9PKScEZ1f/uU/VjjQTyY2tmUEaxtQY6SOPNc0j1GXrCKovMg5RMX 40 | b4DWkWSBB4v540RKsFYCTmaJlGOe85isqYjM28+PHQ07FYFErcaxKm2j/gRI0GD1 41 | +fkEJOBnTI5C3+di0ekCAwEAAaMwMC4wLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/ 42 | AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQBol8+YH7MA 43 | HwnIh7KcJ8h87GkCWsjOJCDJWiYBJArQ0MmgDO0qdx+QEtvLMn3XNtP05ZfK0WyX 44 | or4cWllAkMFYaFbyB2hYazlD1UAAG+22Rku0UP6pJMLbWe6pnqzx+RL68FYdbZhN 45 | fCW2xiiKsdPoo2VEY7eeZKrNr/0RFE5EKXgzmobpTBQT1Dl3Ve4aWLoTy9INlQ/g 46 | z40qS7oq1PjjPLgxINhf4ncJqfmRXugYTOnyFiVXLZTys5Pb9SMKdToGl3NTYWLL 47 | 2AZdjr6bKtT+WtXyHqO0cQ8CkAW0M6VOlMluACllcJxfrtdlQS2S4lUIj76QKBdZ 48 | khBHXq/b8MFX 49 | -----END CERTIFICATE----- 50 | -------------------------------------------------------------------------------- /.kmip/kmip_server/.gitignore: -------------------------------------------------------------------------------- 1 | kmstlsvenv/*/* 2 | .logs/* 3 | pykmip.db 4 | -------------------------------------------------------------------------------- /.kmip/kmip_server/activate_venv.sh: -------------------------------------------------------------------------------- 1 | if [ "Windows_NT" = "$OS" ]; then 2 | PYTHON_BINARY=C:/python/Python38/python.exe 3 | elif command -v /opt/python/3.6/bin/python3; then 4 | PYTHON_BINARY=/opt/python/3.6/bin/python3 5 | elif command -v python3; then 6 | PYTHON_BINARY=python3 7 | elif command -v /opt/mongodbtoolchain/v2/bin/python3; then 8 | PYTHON_BINARY=/opt/mongodbtoolchain/v2/bin/python3 9 | else 10 | echo "error: unable to find a supported python3 executable" 11 | fi 12 | 13 | # create venv on first run 14 | if [ ! -d kmstlsvenv ]; then 15 | FIRST_RUN=1 16 | virtualenv -p ${PYTHON_BINARY} kmstlsvenv 17 | fi 18 | 19 | # always activate venv 20 | if [ "Windows_NT" = "$OS" ]; then 21 | . kmstlsvenv/Scripts/activate 22 | else 23 | . kmstlsvenv/bin/activate 24 | fi 25 | 26 | # install dependencies on first run 27 | if [ ! -z $FIRST_RUN ]; then 28 | CRYPTOGRAPHY_DONT_BUILD_RUST=1 pip install --upgrade boto3~=1.19 cryptography~=3.4.8 pykmip~=0.10.0 29 | fi 30 | -------------------------------------------------------------------------------- /.kmip/kmip_server/kms_kmip_server.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | """ 3 | KMS KMIP test server. 4 | """ 5 | 6 | from kmip.services.server import KmipServer 7 | import os 8 | import logging 9 | import argparse 10 | 11 | HOSTNAME = "localhost" 12 | PORT = 5698 13 | KMIP_SERVER = "kmip_server" 14 | 15 | 16 | def main(): 17 | dir_path = os.path.dirname(os.path.realpath(__file__)) 18 | kmip_dir = os.path.join(dir_path, "..") 19 | default_ca_file = os.path.join( 20 | kmip_dir, "certs", "ca.pem") 21 | default_cert_file = os.path.join( 22 | kmip_dir, "certs", "server.pem") 23 | 24 | parser = argparse.ArgumentParser( 25 | description='MongoDB Mock KMIP KMS Endpoint.') 26 | parser.add_argument('-p', '--port', type=int, 27 | default=PORT, help="Port to listen on") 28 | parser.add_argument('--ca_file', type=str, 29 | default=default_ca_file, help="TLS CA PEM file") 30 | parser.add_argument('--cert_file', type=str, 31 | default=default_cert_file, help="TLS Server PEM file") 32 | args = parser.parse_args() 33 | 34 | server = KmipServer( 35 | hostname=HOSTNAME, 36 | port=args.port, 37 | certificate_path=args.cert_file, 38 | ca_path=args.ca_file, 39 | config_path=None, 40 | auth_suite="TLS1.2", 41 | log_path=os.path.join(kmip_dir, 42 | KMIP_SERVER, ".logs", "pykmip.log"), 43 | database_path=os.path.join( 44 | kmip_dir, KMIP_SERVER, "pykmip.db"), 45 | logging_level=logging.DEBUG, 46 | ) 47 | with server: 48 | print(f"Starting KMS KMIP server on port {args.port}") 49 | server.serve() 50 | 51 | 52 | if __name__ == "__main__": 53 | main() 54 | -------------------------------------------------------------------------------- /.kmip/kmip_server/pykmip.conf: -------------------------------------------------------------------------------- 1 | # Empty config file. PyKMIP client requires a config file. -------------------------------------------------------------------------------- /.kmip/kmip_server/pykmip.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb-university/csfle-guides/fdc3cdfc4890b2bc3e8fd4f40a54a013b7b93a53/.kmip/kmip_server/pykmip.db -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The MongoDB CSFLE Guides Repository is Archived 2 | 3 | > **Warning** 4 | > 5 | > **This repository has been archived and the code is no longer maintained.** 6 | 7 | For MongoDB Field Level Encryption examples maintained by MongoDB, see [mongodb-university/docs-in-use-encryption-examples](https://github.com/mongodb-university/docs-in-use-encryption-examples/). 8 | -------------------------------------------------------------------------------- /dotnet/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | launchSettings.json 8 | .vscode 9 | *.rsuser 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Mono auto generated files 19 | mono_crash.* 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Dd]ebugPublic/ 24 | [Rr]elease/ 25 | [Rr]eleases/ 26 | x64/ 27 | x86/ 28 | [Ww][Ii][Nn]32/ 29 | [Aa][Rr][Mm]/ 30 | [Aa][Rr][Mm]64/ 31 | bld/ 32 | [Bb]in/ 33 | [Oo]bj/ 34 | [Ll]og/ 35 | [Ll]ogs/ 36 | 37 | # Visual Studio 2015/2017 cache/options directory 38 | .vs/ 39 | # Uncomment if you have tasks that create the project's static files in wwwroot 40 | #wwwroot/ 41 | 42 | # Visual Studio 2017 auto generated files 43 | Generated\ Files/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUnit 50 | *.VisualState.xml 51 | TestResult.xml 52 | nunit-*.xml 53 | 54 | # Build Results of an ATL Project 55 | [Dd]ebugPS/ 56 | [Rr]eleasePS/ 57 | dlldata.c 58 | 59 | # Benchmark Results 60 | BenchmarkDotNet.Artifacts/ 61 | 62 | # .NET Core 63 | project.lock.json 64 | project.fragment.lock.json 65 | artifacts/ 66 | 67 | # ASP.NET Scaffolding 68 | ScaffoldingReadMe.txt 69 | 70 | # StyleCop 71 | StyleCopReport.xml 72 | 73 | # Files built by Visual Studio 74 | *_i.c 75 | *_p.c 76 | *_h.h 77 | *.ilk 78 | *.meta 79 | *.obj 80 | *.iobj 81 | *.pch 82 | *.pdb 83 | *.ipdb 84 | *.pgc 85 | *.pgd 86 | *.rsp 87 | *.sbr 88 | *.tlb 89 | *.tli 90 | *.tlh 91 | *.tmp 92 | *.tmp_proj 93 | *_wpftmp.csproj 94 | *.log 95 | *.vspscc 96 | *.vssscc 97 | .builds 98 | *.pidb 99 | *.svclog 100 | *.scc 101 | 102 | # Chutzpah Test files 103 | _Chutzpah* 104 | 105 | # Visual C++ cache files 106 | ipch/ 107 | *.aps 108 | *.ncb 109 | *.opendb 110 | *.opensdf 111 | *.sdf 112 | *.cachefile 113 | *.VC.db 114 | *.VC.VC.opendb 115 | 116 | # Visual Studio profiler 117 | *.psess 118 | *.vsp 119 | *.vspx 120 | *.sap 121 | 122 | # Visual Studio Trace Files 123 | *.e2e 124 | 125 | # TFS 2012 Local Workspace 126 | $tf/ 127 | 128 | # Guidance Automation Toolkit 129 | *.gpState 130 | 131 | # ReSharper is a .NET coding add-in 132 | _ReSharper*/ 133 | *.[Rr]e[Ss]harper 134 | *.DotSettings.user 135 | 136 | # TeamCity is a build add-in 137 | _TeamCity* 138 | 139 | # DotCover is a Code Coverage Tool 140 | *.dotCover 141 | 142 | # AxoCover is a Code Coverage Tool 143 | .axoCover/* 144 | !.axoCover/settings.json 145 | 146 | # Coverlet is a free, cross platform Code Coverage Tool 147 | coverage*.json 148 | coverage*.xml 149 | coverage*.info 150 | 151 | # Visual Studio code coverage results 152 | *.coverage 153 | *.coveragexml 154 | 155 | # NCrunch 156 | _NCrunch_* 157 | .*crunch*.local.xml 158 | nCrunchTemp_* 159 | 160 | # MightyMoose 161 | *.mm.* 162 | AutoTest.Net/ 163 | 164 | # Web workbench (sass) 165 | .sass-cache/ 166 | 167 | # Installshield output folder 168 | [Ee]xpress/ 169 | 170 | # DocProject is a documentation generator add-in 171 | DocProject/buildhelp/ 172 | DocProject/Help/*.HxT 173 | DocProject/Help/*.HxC 174 | DocProject/Help/*.hhc 175 | DocProject/Help/*.hhk 176 | DocProject/Help/*.hhp 177 | DocProject/Help/Html2 178 | DocProject/Help/html 179 | 180 | # Click-Once directory 181 | publish/ 182 | 183 | # Publish Web Output 184 | *.[Pp]ublish.xml 185 | *.azurePubxml 186 | # Note: Comment the next line if you want to checkin your web deploy settings, 187 | # but database connection strings (with potential passwords) will be unencrypted 188 | *.pubxml 189 | *.publishproj 190 | 191 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 192 | # checkin your Azure Web App publish settings, but sensitive information contained 193 | # in these scripts will be unencrypted 194 | PublishScripts/ 195 | 196 | # NuGet Packages 197 | *.nupkg 198 | # NuGet Symbol Packages 199 | *.snupkg 200 | # The packages folder can be ignored because of Package Restore 201 | **/[Pp]ackages/* 202 | # except build/, which is used as an MSBuild target. 203 | !**/[Pp]ackages/build/ 204 | # Uncomment if necessary however generally it will be regenerated when needed 205 | #!**/[Pp]ackages/repositories.config 206 | # NuGet v3's project.json files produces more ignorable files 207 | *.nuget.props 208 | *.nuget.targets 209 | 210 | # Microsoft Azure Build Output 211 | csx/ 212 | *.build.csdef 213 | 214 | # Microsoft Azure Emulator 215 | ecf/ 216 | rcf/ 217 | 218 | # Windows Store app package directories and files 219 | AppPackages/ 220 | BundleArtifacts/ 221 | Package.StoreAssociation.xml 222 | _pkginfo.txt 223 | *.appx 224 | *.appxbundle 225 | *.appxupload 226 | 227 | # Visual Studio cache files 228 | # files ending in .cache can be ignored 229 | *.[Cc]ache 230 | # but keep track of directories ending in .cache 231 | !?*.[Cc]ache/ 232 | 233 | # Others 234 | ClientBin/ 235 | ~$* 236 | *~ 237 | *.dbmdl 238 | *.dbproj.schemaview 239 | *.jfm 240 | *.pfx 241 | *.publishsettings 242 | orleans.codegen.cs 243 | 244 | # Including strong name files can present a security risk 245 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 246 | #*.snk 247 | 248 | # Since there are multiple workflows, uncomment next line to ignore bower_components 249 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 250 | #bower_components/ 251 | 252 | # RIA/Silverlight projects 253 | Generated_Code/ 254 | 255 | # Backup & report files from converting an old project file 256 | # to a newer Visual Studio version. Backup files are not needed, 257 | # because we have git ;-) 258 | _UpgradeReport_Files/ 259 | Backup*/ 260 | UpgradeLog*.XML 261 | UpgradeLog*.htm 262 | ServiceFabricBackup/ 263 | *.rptproj.bak 264 | 265 | # SQL Server files 266 | *.mdf 267 | *.ldf 268 | *.ndf 269 | 270 | # Business Intelligence projects 271 | *.rdl.data 272 | *.bim.layout 273 | *.bim_*.settings 274 | *.rptproj.rsuser 275 | *- [Bb]ackup.rdl 276 | *- [Bb]ackup ([0-9]).rdl 277 | *- [Bb]ackup ([0-9][0-9]).rdl 278 | 279 | # Microsoft Fakes 280 | FakesAssemblies/ 281 | 282 | # GhostDoc plugin setting file 283 | *.GhostDoc.xml 284 | 285 | # Node.js Tools for Visual Studio 286 | .ntvs_analysis.dat 287 | node_modules/ 288 | 289 | # Visual Studio 6 build log 290 | *.plg 291 | 292 | # Visual Studio 6 workspace options file 293 | *.opt 294 | 295 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 296 | *.vbw 297 | 298 | # Visual Studio LightSwitch build output 299 | **/*.HTMLClient/GeneratedArtifacts 300 | **/*.DesktopClient/GeneratedArtifacts 301 | **/*.DesktopClient/ModelManifest.xml 302 | **/*.Server/GeneratedArtifacts 303 | **/*.Server/ModelManifest.xml 304 | _Pvt_Extensions 305 | 306 | # Paket dependency manager 307 | .paket/paket.exe 308 | paket-files/ 309 | 310 | # FAKE - F# Make 311 | .fake/ 312 | 313 | # CodeRush personal settings 314 | .cr/personal 315 | 316 | # Python Tools for Visual Studio (PTVS) 317 | __pycache__/ 318 | *.pyc 319 | 320 | # Cake - Uncomment if you are using it 321 | # tools/** 322 | # !tools/packages.config 323 | 324 | # Tabs Studio 325 | *.tss 326 | 327 | # Telerik's JustMock configuration file 328 | *.jmconfig 329 | 330 | # BizTalk build output 331 | *.btp.cs 332 | *.btm.cs 333 | *.odx.cs 334 | *.xsd.cs 335 | 336 | # OpenCover UI analysis results 337 | OpenCover/ 338 | 339 | # Azure Stream Analytics local run output 340 | ASALocalRun/ 341 | 342 | # MSBuild Binary and Structured Log 343 | *.binlog 344 | 345 | # NVidia Nsight GPU debugger configuration file 346 | *.nvuser 347 | 348 | # MFractors (Xamarin productivity tool) working folder 349 | .mfractor/ 350 | 351 | # Local History for Visual Studio 352 | .localhistory/ 353 | 354 | # BeatPulse healthcheck temp database 355 | healthchecksdb 356 | 357 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 358 | MigrationBackup/ 359 | 360 | # Ionide (cross platform F# VS Code tools) working folder 361 | .ionide/ 362 | 363 | # Fody - auto-generated XML schema 364 | FodyWeavers.xsd 365 | -------------------------------------------------------------------------------- /dotnet/CSFLE.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29424.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSFLE", "CSFLE\CSFLE.csproj", "{BB3F45A1-2591-4E9A-AAB4-CD4B2DE1D665}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {BB3F45A1-2591-4E9A-AAB4-CD4B2DE1D665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {BB3F45A1-2591-4E9A-AAB4-CD4B2DE1D665}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {BB3F45A1-2591-4E9A-AAB4-CD4B2DE1D665}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {BB3F45A1-2591-4E9A-AAB4-CD4B2DE1D665}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {EBFBC79F-4FB9-4693-9777-1722F2A17939} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /dotnet/CSFLE/AutoEncryptionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using MongoDB.Bson; 5 | using MongoDB.Driver; 6 | using MongoDB.Driver.Encryption; 7 | 8 | namespace CSFLE 9 | { 10 | public class AutoEncryptionHelper 11 | { 12 | private static readonly string __localMasterKeyPath = "../../../master-key.txt"; 13 | private static readonly string __sampleNameValue = "John Doe"; 14 | private static readonly int __sampleSsnValue = 145014000; 15 | 16 | private static readonly BsonDocument __sampleDocFields = 17 | new BsonDocument 18 | { 19 | { "name", __sampleNameValue }, 20 | { "ssn", __sampleSsnValue }, 21 | { "bloodType", "AB-" }, 22 | { 23 | "medicalRecords", 24 | new BsonArray(new [] 25 | { 26 | new BsonDocument("weight", 180), 27 | new BsonDocument("bloodPressure", "120/80") 28 | }) 29 | }, 30 | { 31 | "insurance", 32 | new BsonDocument 33 | { 34 | { "policyNumber", 123142 }, 35 | { "provider", "MaestCare" } 36 | } 37 | } 38 | }; 39 | 40 | private readonly string _connectionString; 41 | private readonly CollectionNamespace _keyVaultNamespace; 42 | private readonly CollectionNamespace _medicalRecordsNamespace; 43 | 44 | public AutoEncryptionHelper(string connectionString, CollectionNamespace keyVaultNamespace) 45 | { 46 | _connectionString = connectionString; 47 | _keyVaultNamespace = keyVaultNamespace; 48 | _medicalRecordsNamespace = CollectionNamespace.FromFullName("medicalRecords.patients"); 49 | } 50 | 51 | public void EncryptedWriteAndRead(string keyIdBase64, KmsKeyLocation kmsKeyLocation) 52 | { 53 | // Construct a JSON Schema 54 | var schema = JsonSchemaCreator.CreateJsonSchema(keyIdBase64); 55 | 56 | // Construct an auto-encrypting client 57 | var autoEncryptingClient = CreateAutoEncryptingClient( 58 | kmsKeyLocation, 59 | _keyVaultNamespace, 60 | schema); 61 | var collection = autoEncryptingClient 62 | .GetDatabase(_medicalRecordsNamespace.DatabaseNamespace.DatabaseName) 63 | .GetCollection(_medicalRecordsNamespace.CollectionName); 64 | 65 | var ssnQuery = Builders.Filter.Eq("ssn", __sampleSsnValue); 66 | 67 | // Insert a document into the collection 68 | collection.UpdateOne(ssnQuery, new BsonDocument("$set", __sampleDocFields), new UpdateOptions() { IsUpsert = true }); 69 | Console.WriteLine("Successfully upserted the sample document!"); 70 | 71 | // Query by SSN field with auto-encrypting client 72 | var result = collection.Find(ssnQuery).Single(); 73 | 74 | Console.WriteLine($"Encrypted client query by the SSN (deterministically-encrypted) field:\n {result}\n"); 75 | } 76 | 77 | public void QueryWithNonEncryptedClient() 78 | { 79 | var nonAutoEncryptingClient = new MongoClient(_connectionString); 80 | var collection = nonAutoEncryptingClient 81 | .GetDatabase(_medicalRecordsNamespace.DatabaseNamespace.DatabaseName) 82 | .GetCollection(_medicalRecordsNamespace.CollectionName); 83 | var ssnQuery = Builders.Filter.Eq("ssn", __sampleSsnValue); 84 | 85 | var result = collection.Find(ssnQuery).FirstOrDefault(); 86 | if (result != null) 87 | { 88 | throw new Exception("Expected no document to be found but one was found."); 89 | } 90 | 91 | // Query by name field with a normal non-auto-encrypting client 92 | var nameQuery = Builders.Filter.Eq("name", __sampleNameValue); 93 | result = collection.Find(nameQuery).FirstOrDefault(); 94 | if (result == null) 95 | { 96 | throw new Exception("Expected the document to be found but none was found."); 97 | } 98 | 99 | Console.WriteLine($"Query by name (non-encrypted field) using non-auto-encrypting client returned:\n {result}\n"); 100 | } 101 | 102 | private IMongoClient CreateAutoEncryptingClient( 103 | KmsKeyLocation kmsKeyLocation, 104 | CollectionNamespace keyVaultNamespace, 105 | BsonDocument schema) 106 | { 107 | var kmsProviders = new Dictionary>(); 108 | 109 | switch (kmsKeyLocation) 110 | { 111 | case KmsKeyLocation.Local: 112 | var localMasterKeyBase64 = File.ReadAllText(__localMasterKeyPath); 113 | var localMasterKeyBytes = Convert.FromBase64String(localMasterKeyBase64); 114 | var localOptions = new Dictionary 115 | { 116 | { "key", localMasterKeyBytes } 117 | }; 118 | kmsProviders.Add("local", localOptions); 119 | break; 120 | 121 | case KmsKeyLocation.AWS: 122 | var awsAccessKey = Environment.GetEnvironmentVariable("FLE_AWS_ACCESS_KEY"); 123 | var awsSecretAccessKey = Environment.GetEnvironmentVariable("FLE_AWS_SECRET_ACCESS_KEY"); 124 | var awsKmsOptions = new Dictionary 125 | { 126 | { "accessKeyId", awsAccessKey }, 127 | { "secretAccessKey", awsSecretAccessKey } 128 | }; 129 | kmsProviders.Add("aws", awsKmsOptions); 130 | break; 131 | 132 | case KmsKeyLocation.Azure: 133 | var azureTenantId = Environment.GetEnvironmentVariable("FLE_AZURE_TENANT_ID"); 134 | var azureClientId = Environment.GetEnvironmentVariable("FLE_AZURE_CLIENT_ID"); 135 | var azureClientSecret = Environment.GetEnvironmentVariable("FLE_AZURE_CLIENT_SECRET"); 136 | var azureIdentityPlatformEndpoint = Environment.GetEnvironmentVariable("FLE_AZURE_IDENTIFY_PLATFORM_ENPDOINT"); // Optional, only needed if user is using a non-commercial Azure instance 137 | 138 | var azureKmsOptions = new Dictionary 139 | { 140 | { "tenantId", azureTenantId }, 141 | { "clientId", azureClientId }, 142 | { "clientSecret", azureClientSecret }, 143 | }; 144 | if (azureIdentityPlatformEndpoint != null) 145 | { 146 | azureKmsOptions.Add("identityPlatformEndpoint", azureIdentityPlatformEndpoint); 147 | } 148 | kmsProviders.Add("azure", azureKmsOptions); 149 | break; 150 | 151 | case KmsKeyLocation.GCP: 152 | var gcpPrivateKey = Environment.GetEnvironmentVariable("FLE_GCP_PRIVATE_KEY"); 153 | var gcpEmail = Environment.GetEnvironmentVariable("FLE_GCP_EMAIL"); 154 | var gcpKmsOptions = new Dictionary 155 | { 156 | { "privateKey", gcpPrivateKey }, 157 | { "email", gcpEmail }, 158 | }; 159 | kmsProviders.Add("gcp", gcpKmsOptions); 160 | break; 161 | } 162 | 163 | var schemaMap = new Dictionary(); 164 | schemaMap.Add(_medicalRecordsNamespace.ToString(), schema); 165 | 166 | var extraOptions = new Dictionary() 167 | { 168 | // uncomment the following line if you are running mongocryptd manually 169 | // { "mongocryptdBypassSpawn", true } 170 | }; 171 | 172 | var clientSettings = MongoClientSettings.FromConnectionString(_connectionString); 173 | var autoEncryptionOptions = new AutoEncryptionOptions( 174 | keyVaultNamespace: keyVaultNamespace, 175 | kmsProviders: kmsProviders, 176 | schemaMap: schemaMap, 177 | extraOptions: extraOptions); 178 | clientSettings.AutoEncryptionOptions = autoEncryptionOptions; 179 | return new MongoClient(clientSettings); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /dotnet/CSFLE/CSFLE.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /dotnet/CSFLE/JsonSchemaCreator.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using System; 3 | 4 | namespace CSFLE 5 | { 6 | public static class JsonSchemaCreator 7 | { 8 | private static readonly string DETERMINISTIC_ENCRYPTION_TYPE = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"; 9 | private static readonly string RANDOM_ENCRYPTION_TYPE = "AEAD_AES_256_CBC_HMAC_SHA_512-Random"; 10 | 11 | private static BsonDocument CreateEncryptMetadata(string keyIdBase64) 12 | { 13 | var keyId = new BsonBinaryData(Convert.FromBase64String(keyIdBase64), BsonBinarySubType.UuidStandard); 14 | return new BsonDocument("keyId", new BsonArray(new[] { keyId })); 15 | } 16 | 17 | private static BsonDocument CreateEncryptedField(string bsonType, bool isDeterministic) 18 | { 19 | return new BsonDocument 20 | { 21 | { 22 | "encrypt", 23 | new BsonDocument 24 | { 25 | { "bsonType", bsonType }, 26 | { "algorithm", isDeterministic ? DETERMINISTIC_ENCRYPTION_TYPE : RANDOM_ENCRYPTION_TYPE} 27 | } 28 | } 29 | }; 30 | } 31 | 32 | public static BsonDocument CreateJsonSchema(string keyId) 33 | { 34 | return new BsonDocument 35 | { 36 | { "bsonType", "object" }, 37 | { "encryptMetadata", CreateEncryptMetadata(keyId) }, 38 | { 39 | "properties", 40 | new BsonDocument 41 | { 42 | 43 | { "ssn", CreateEncryptedField("int", true) }, 44 | { "bloodType", CreateEncryptedField("string", false) }, 45 | { "medicalRecords", CreateEncryptedField("array", false) }, 46 | { 47 | "insurance", 48 | new BsonDocument 49 | { 50 | { "bsonType", "object" }, 51 | { 52 | "properties", 53 | new BsonDocument 54 | { 55 | { "policyNumber", CreateEncryptedField("int", true) } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | }; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /dotnet/CSFLE/KmsKeyHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading; 5 | using MongoDB.Bson; 6 | using MongoDB.Driver.Encryption; 7 | using MongoDB.Driver; 8 | 9 | namespace CSFLE 10 | { 11 | public class KmsKeyHelper 12 | { 13 | private readonly static string __localMasterKeyPath = "../../../master-key.txt"; 14 | 15 | private readonly string _connectionString; 16 | private readonly CollectionNamespace _keyVaultNamespace; 17 | 18 | public KmsKeyHelper( 19 | string connectionString, 20 | CollectionNamespace keyVaultNamespace) 21 | { 22 | _connectionString = connectionString; 23 | _keyVaultNamespace = keyVaultNamespace; 24 | } 25 | 26 | public void GenerateLocalMasterKey() 27 | { 28 | using (var randomNumberGenerator = System.Security.Cryptography.RandomNumberGenerator.Create()) 29 | { 30 | var bytes = new byte[96]; 31 | randomNumberGenerator.GetBytes(bytes); 32 | var localMasterKeyBase64 = Convert.ToBase64String(bytes); 33 | Console.WriteLine(localMasterKeyBase64); 34 | File.WriteAllText(__localMasterKeyPath, localMasterKeyBase64); 35 | } 36 | } 37 | 38 | public string CreateKeyWithLocalKmsProvider() 39 | { 40 | string localMasterKeyBase64 = File.ReadAllText(__localMasterKeyPath); 41 | var localMasterKeyBytes = Convert.FromBase64String(localMasterKeyBase64); 42 | 43 | var kmsProviders = new Dictionary>(); 44 | var localOptions = new Dictionary 45 | { 46 | { "key", localMasterKeyBytes } 47 | }; 48 | kmsProviders.Add("local", localOptions); 49 | 50 | var clientEncryption = GetClientEncryption(kmsProviders); 51 | var dataKeyId = clientEncryption.CreateDataKey("local", new DataKeyOptions(), CancellationToken.None); 52 | Console.WriteLine($"Local DataKeyId [UUID]: {dataKeyId}"); 53 | var dataKeyIdBase64 = Convert.ToBase64String(GuidConverter.ToBytes(dataKeyId, GuidRepresentation.Standard)); 54 | Console.WriteLine($"Local DataKeyId [base64]: {dataKeyIdBase64}"); 55 | 56 | ValidateKey(dataKeyId); 57 | return dataKeyIdBase64; 58 | } 59 | 60 | public string CreateKeyWithAwsKmsProvider() 61 | { 62 | var kmsProviders = new Dictionary>(); 63 | 64 | var awsAccessKey = Environment.GetEnvironmentVariable("FLE_AWS_ACCESS_KEY"); 65 | var awsSecretAccessKey = Environment.GetEnvironmentVariable("FLE_AWS_SECRET_ACCESS_KEY"); 66 | var awsKmsOptions = new Dictionary 67 | { 68 | { "accessKeyId", awsAccessKey }, 69 | { "secretAccessKey", awsSecretAccessKey } 70 | }; 71 | kmsProviders.Add("aws", awsKmsOptions); 72 | 73 | var clientEncryption = GetClientEncryption(kmsProviders); 74 | 75 | var awsKeyARN = Environment.GetEnvironmentVariable("FLE_AWS_KEY_ARN"); // e.g. "arn:aws:kms:us-east-2:111122223333:alias/test-key" 76 | var awsKeyRegion = Environment.GetEnvironmentVariable("FLE_AWS_KEY_REGION"); 77 | var awsEndpoint = Environment.GetEnvironmentVariable("FLE_AWS_ENDPOINT"); // Optional, AWS KMS URL. 78 | var dataKeyOptions = new DataKeyOptions( 79 | masterKey: new BsonDocument 80 | { 81 | { "region", awsKeyRegion }, 82 | { "key", awsKeyARN }, 83 | { "endpoint", () => awsEndpoint, awsEndpoint != null } 84 | }); 85 | 86 | var dataKeyId = clientEncryption.CreateDataKey("aws", dataKeyOptions, CancellationToken.None); 87 | Console.WriteLine($"AWS DataKeyId [UUID]: {dataKeyId}"); 88 | var dataKeyIdBase64 = Convert.ToBase64String(GuidConverter.ToBytes(dataKeyId, GuidRepresentation.Standard)); 89 | Console.WriteLine($"AWS DataKeyId [base64]: {dataKeyIdBase64}"); 90 | 91 | ValidateKey(dataKeyId); 92 | return dataKeyIdBase64; 93 | } 94 | 95 | public string CreateKeyWithAzureKmsProvider() 96 | { 97 | var kmsProviders = new Dictionary>(); 98 | 99 | var azureTenantId = Environment.GetEnvironmentVariable("FLE_AZURE_TENANT_ID"); 100 | var azureClientId = Environment.GetEnvironmentVariable("FLE_AZURE_CLIENT_ID"); 101 | var azureClientSecret = Environment.GetEnvironmentVariable("FLE_AZURE_CLIENT_SECRET"); 102 | var azureIdentityPlatformEndpoint = Environment.GetEnvironmentVariable("FLE_AZURE_IDENTIFY_PLATFORM_ENPDOINT"); // Optional, only needed if user is using a non-commercial Azure instance 103 | 104 | var azureKmsOptions = new Dictionary 105 | { 106 | { "tenantId", azureTenantId }, 107 | { "clientId", azureClientId }, 108 | { "clientSecret", azureClientSecret }, 109 | }; 110 | if (azureIdentityPlatformEndpoint != null) 111 | { 112 | azureKmsOptions.Add("identityPlatformEndpoint", azureIdentityPlatformEndpoint); 113 | } 114 | kmsProviders.Add("azure", azureKmsOptions); 115 | 116 | var clientEncryption = GetClientEncryption(kmsProviders); 117 | var azureKeyName = Environment.GetEnvironmentVariable("FLE_AZURE_KEY_NAME"); 118 | var azureKeyVaultEndpoint = Environment.GetEnvironmentVariable("FLE_AZURE_KEYVAULT_ENDPOINT"); // typically .vault.azure.net 119 | var azureKeyVersion = Environment.GetEnvironmentVariable("FLE_AZURE_KEY_VERSION"); // Optional 120 | var dataKeyOptions = new DataKeyOptions( 121 | masterKey: new BsonDocument 122 | { 123 | { "keyName", azureKeyName }, 124 | { "keyVaultEndpoint", azureKeyVaultEndpoint }, 125 | { "keyVersion", () => azureKeyVersion, azureKeyVersion != null } 126 | }); 127 | 128 | var dataKeyId = clientEncryption.CreateDataKey("azure", dataKeyOptions, CancellationToken.None); 129 | Console.WriteLine($"Azure DataKeyId [UUID]: {dataKeyId}"); 130 | var dataKeyIdBase64 = Convert.ToBase64String(GuidConverter.ToBytes(dataKeyId, GuidRepresentation.Standard)); 131 | Console.WriteLine($"Azure DataKeyId [base64]: {dataKeyIdBase64}"); 132 | 133 | ValidateKey(dataKeyId); 134 | return dataKeyIdBase64; 135 | } 136 | 137 | public string CreateKeyWithGcpKmsProvider() 138 | { 139 | var kmsProviders = new Dictionary>(); 140 | 141 | var gcpPrivateKey = Environment.GetEnvironmentVariable("FLE_GCP_PRIVATE_KEY"); 142 | var gcpEmail = Environment.GetEnvironmentVariable("FLE_GCP_EMAIL"); 143 | var gcpEndpoint = Environment.GetEnvironmentVariable("FLE_GCP_IDENTITY_ENDPOINT"); // Optional, defaults to "oauth2.googleapis.com". 144 | var gcpKmsOptions = new Dictionary 145 | { 146 | { "privateKey", gcpPrivateKey }, 147 | { "email", gcpEmail }, 148 | }; 149 | if (gcpEndpoint != null) 150 | { 151 | gcpKmsOptions.Add("endpoint", gcpEndpoint); 152 | } 153 | kmsProviders.Add("gcp", gcpKmsOptions); 154 | 155 | var clientEncryption = GetClientEncryption(kmsProviders); 156 | var gcpDataKeyProjectId = Environment.GetEnvironmentVariable("FLE_GCP_PROJ_ID"); 157 | var gcpDataKeyLocation = Environment.GetEnvironmentVariable("FLE_GCP_KEY_LOC"); // Optional. e.g. "global" 158 | var gcpDataKeyKeyRing = Environment.GetEnvironmentVariable("FLE_GCP_KEY_RING"); 159 | var gcpDataKeyKeyName = Environment.GetEnvironmentVariable("FLE_GCP_KEY_NAME"); 160 | var gcpDataKeyKeyVersion = Environment.GetEnvironmentVariable("FLE_GCP_KEY_VERSION"); // Optional 161 | var gcpDataKeyEndpoint = Environment.GetEnvironmentVariable("FLE_GCP_KMS_ENDPOINT"); // Optional, KMS URL, defaults to https://www.googleapis.com/auth/cloudkms 162 | 163 | var dataKeyOptions = new DataKeyOptions( 164 | masterKey: new BsonDocument 165 | { 166 | { "projectId", gcpDataKeyProjectId }, 167 | { "location", gcpDataKeyLocation } , 168 | { "keyRing", gcpDataKeyKeyRing }, 169 | { "keyName", gcpDataKeyKeyName }, 170 | { "keyVersion", () => gcpDataKeyKeyVersion, gcpDataKeyKeyVersion != null }, 171 | { "endpoint", () => gcpDataKeyEndpoint, gcpDataKeyEndpoint != null } 172 | }); 173 | 174 | var dataKeyId = clientEncryption.CreateDataKey("gcp", dataKeyOptions, CancellationToken.None); 175 | Console.WriteLine($"DataKeyId [UUID]: {dataKeyId}"); 176 | var dataKeyIdBase64 = Convert.ToBase64String(GuidConverter.ToBytes(dataKeyId, GuidRepresentation.Standard)); 177 | Console.WriteLine($"DataKeyId [base64]: {dataKeyIdBase64}"); 178 | 179 | ValidateKey(dataKeyId); 180 | return dataKeyIdBase64; 181 | } 182 | 183 | private ClientEncryption GetClientEncryption( 184 | Dictionary> kmsProviders) 185 | { 186 | var keyVaultClient = new MongoClient(_connectionString); 187 | var clientEncryptionOptions = new ClientEncryptionOptions( 188 | keyVaultClient: keyVaultClient, 189 | keyVaultNamespace: _keyVaultNamespace, 190 | kmsProviders: kmsProviders); 191 | 192 | return new ClientEncryption(clientEncryptionOptions); 193 | } 194 | 195 | private void ValidateKey(Guid dataKeyId) 196 | { 197 | // Verify that the key document was created 198 | var client = new MongoClient(_connectionString); 199 | var collection = client 200 | .GetDatabase(_keyVaultNamespace.DatabaseNamespace.DatabaseName) 201 | .GetCollection( 202 | _keyVaultNamespace.CollectionName, 203 | new MongoCollectionSettings 204 | { 205 | #pragma warning disable CS0618 206 | GuidRepresentation = GuidRepresentation.Standard 207 | #pragma warning restore CS0618 208 | }); 209 | var query = Builders.Filter.Eq("_id", new BsonBinaryData(dataKeyId, GuidRepresentation.Standard)); 210 | var keyDocument = collection 211 | .Find(query) 212 | .Single(); 213 | 214 | Console.WriteLine(keyDocument); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /dotnet/CSFLE/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MongoDB.Driver; 3 | 4 | namespace CSFLE 5 | { 6 | public enum KmsKeyLocation 7 | { 8 | AWS, 9 | Azure, 10 | GCP, 11 | Local 12 | } 13 | 14 | class Program 15 | { 16 | static void Main(string[] args) 17 | { 18 | var connectionString = "mongodb://localhost:27017"; 19 | var keyVaultNamespace = CollectionNamespace.FromFullName("encryption.__keyVault"); 20 | 21 | var kmsKeyHelper = new KmsKeyHelper( 22 | connectionString: connectionString, 23 | keyVaultNamespace: keyVaultNamespace); 24 | var autoEncryptionHelper = new AutoEncryptionHelper( 25 | connectionString: connectionString, 26 | keyVaultNamespace: keyVaultNamespace); 27 | 28 | string kmsKeyIdBase64; 29 | //Only run GenerateLocalMasterKey() once 30 | //kmsKeyHelper.GenerateLocalMasterKey(); 31 | 32 | //Local 33 | kmsKeyIdBase64 = kmsKeyHelper.CreateKeyWithLocalKmsProvider(); 34 | autoEncryptionHelper.EncryptedWriteAndRead(kmsKeyIdBase64, KmsKeyLocation.Local); 35 | 36 | // AWS 37 | //kmsKeyIdBase64 = kmsKeyHelper.CreateKeyWithAwsKmsProvider(); 38 | //autoEncryptionHelper.EncryptedWriteAndRead(kmsKeyIdBase64, KmsKeyLocation.AWS); 39 | 40 | // Azure 41 | //kmsKeyIdBase64 = kmsKeyHelper.CreateKeyWithAzureKmsProvider(); 42 | //autoEncryptionHelper.EncryptedWriteAndRead(kmsKeyIdBase64, KmsKeyLocation.Azure); 43 | 44 | // GCP 45 | //kmsKeyIdBase64 = kmsKeyHelper.CreateKeyWithGcpKmsProvider(); 46 | //autoEncryptionHelper.EncryptedWriteAndRead(kmsKeyIdBase64, KmsKeyLocation.GCP); 47 | 48 | autoEncryptionHelper.QueryWithNonEncryptedClient(); 49 | 50 | Console.ReadKey(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dotnet/CSFLE/master-key.txt: -------------------------------------------------------------------------------- 1 | z6y8NecZcrC8zkCb8phTLp/hdnJpABsQ643xVgr5C0/pfO/p7jjYkqTXgzvsyYCZS/xvexE+U7BHAxZB4GmvD3aoSz3LJ3D+TQyBbj5R79cQL9s4WUxG8tvPhR/gOfRO -------------------------------------------------------------------------------- /dotnet/README.md: -------------------------------------------------------------------------------- 1 | # .NET CSFLE Example 2 | 3 | ## Windows Only 4 | 5 | Currently, remote KMS support is Windows only. 6 | 7 | ## Steps 8 | 9 | 1. Clone this repository: 10 | 11 | ``` 12 | git clone https://github.com/mongodb-university/csfle-guides.git 13 | ``` 14 | 15 | Navigate to the CSFLE directory. Work from this directory for the remainder of these 16 | instructions. 17 | 18 | 2. Start a `mongod` instance (Enterprise version >= 4.2) running on port 27017 19 | 20 | 3. Make sure you have the `master-key.txt` file in the root of your 21 | execution environment if you are using a local KMS. This is a 96-byte cryptographically-secure generated 22 | master encryption key required to run this example project. To generate your 23 | own master key or use a KMS, refer to the 24 | [CSFLE Use Case Guide](https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-guide/). 25 | 26 | 4. Configure variables for your preferred remote KMS, if 27 | necessary. Out of the box, this application looks for KMS credentials and data key options 28 | via the following environmental variables: 29 | 30 | - AWS 31 | 32 | - KMS Credentials 33 | 34 | - FLE_AWS_ACCESS_KEY 35 | - FLE_AWS_SECRET_ACCESS_KEY 36 | 37 | - Master Key Options 38 | 39 | - FLE_AWS_KEY_ARN 40 | - FLE_AWS_KEY_REGION 41 | - FLE_AWS_ENDPOINT (Optional) 42 | 43 | - GCP 44 | 45 | - KMS Credentials 46 | 47 | - FLE_GCP_EMAIL 48 | - FLE_GCP_PRIVATE_KEY 49 | - FLE_GCP_IDENTITY_ENDPOINT (Optional) 50 | 51 | - Master Key Options 52 | 53 | - FLE_GCP_KEY_NAME 54 | - FLE_GCP_KEY_RING 55 | - FLE_GCP_PROJ_ID 56 | - FLE_GCP_KEY_LOC (Optional) 57 | - FLE_GCP_KEY_VERSION (Optional) 58 | - FLE_GCP_KMS_ENDPOINT (Optional) 59 | 60 | - Azure 61 | 62 | - KMS Credentials 63 | 64 | - FLE_AZURE_CLIENT_ID 65 | - FLE_AZURE_CLIENT_SECRET 66 | - FLE_AZURE_TENANT_ID 67 | - FLE_AZURE_IDENTIFY_PLATFORM_ENPDOINT (Optional) 68 | 69 | - Master Key Options 70 | 71 | - FLE_AZURE_KEY_NAME 72 | - FLE_AZURE_KEYVAULT_ENDPOINT 73 | - FLE_AZURE_KEY_VERSION (Optional) 74 | 75 | 5. In `Program.cs` you'll find methods for the supported KMS providers. Uncomment 76 | your preferred provider and run the program. 77 | -------------------------------------------------------------------------------- /go/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # Local environment 18 | .env 19 | -------------------------------------------------------------------------------- /go/README.md: -------------------------------------------------------------------------------- 1 | # Go CSFLE Example 2 | 3 | ## Steps 4 | 5 | 1. Clone this repository: 6 | 7 | ```sh 8 | git clone https://github.com/mongodb-university/csfle-guides.git 9 | cd csfle-guides/go 10 | ``` 11 | 12 | Work from the `go` directory for the remainder of these 13 | instructions. 14 | 15 | 2. Start a `mongod` instance (Enterprise version >= 4.2) running on port 27017 16 | 17 | 3. Make sure you have the `master-key.txt` file in the root of your 18 | execution environment. This is a 96-byte cryptographically-secure generated 19 | master encryption key required to run this example project. To generate your 20 | own master key or use a KMS, refer to the [CSFLE Use Case 21 | Guide](https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-guide/). 22 | 23 | The settings for each supported KMS are located in 24 | `kms/provider.go`. In this project we have used [godotenv](https://github.com/joho/godotenv) to 25 | load our environmental variables. Feel free to replace the calls to `GetCheckedEnv` with string values if you 26 | prefer. 27 | 28 | 4. If `mongocryptd` is not installed in your system path, ensure you specify its 29 | location in `csfle/encrypted_client.go` in the `EncryptedClient` function. 30 | 31 | 5. Uncomment the initializer for the KMS provider you would like to use in `main.go` 32 | 33 | ```go 34 | // preferredProvider := kms.AWSProvider() 35 | // preferredProvider := kms.AzureProvider() 36 | // preferredProvider := kms.GCPProvider() 37 | preferredProvider := kms.LocalProvider(localMasterKey()) 38 | ``` 39 | 40 | 6. Run the `go run -tags=cse main.go`. This does the following: 41 | 42 | - When using a local master key 43 | 44 | - Reads in the local key from `master-key.txt` 45 | 46 | - When using a Key Management System (KMS) 47 | 48 | - Reads in configuration information for your preferred KMS. 49 | - Fetches the master key from the KMS. 50 | 51 | - When using either 52 | 53 | - Creates a data key in the **keyVault** collection. 54 | - Uses this newly created data key to finish setting up JSON schema for automatic encryption. 55 | - Creates a new, encrypted client configured for automatic Client-Side Field Level Encryption (CSFLE). 56 | - Drops the `medicalRecords.patients` collection. 57 | - Inserts a sample document with the encrypted client. 58 | - Issues a find operation with the encrypted client and prints it out, showing the document in unencrypted form. 59 | - Issues a find operation with an unencrypted client and prints it out, showing the fields specified for encryption are unreadable. 60 | 61 | 7. Update the code to insert a document with the regular client. What happens? 62 | -------------------------------------------------------------------------------- /go/csfle/data_key.go: -------------------------------------------------------------------------------- 1 | package csfle 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/mongodb-university/csfle-guides/go/kms" 10 | "go.mongodb.org/mongo-driver/bson" 11 | "go.mongodb.org/mongo-driver/bson/primitive" 12 | "go.mongodb.org/mongo-driver/mongo" 13 | "go.mongodb.org/mongo-driver/mongo/options" 14 | ) 15 | 16 | // GetDataKey creates a new data key and returns the base64 encoding to be used 17 | // in schema configuration for automatic encryption 18 | func GetDataKey(keyVaultNamespace, uri, keyAltName string, provider kms.Provider) (string, error) { 19 | 20 | // configuring encryption options by setting the keyVault namespace and the kms providers information 21 | // we configure this client to fetch the master key so that we can 22 | // create a data key in the next step 23 | clientEncryptionOpts := options.ClientEncryption().SetKeyVaultNamespace(keyVaultNamespace).SetKmsProviders(provider.Credentials()) 24 | keyVaultClient, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri)) 25 | if err != nil { 26 | return "", fmt.Errorf("Client encryption connect error %v", err) 27 | } 28 | clientEnc, err := mongo.NewClientEncryption(keyVaultClient, clientEncryptionOpts) 29 | if err != nil { 30 | return "", fmt.Errorf("NewClientEncryption error %v", err) 31 | } 32 | defer func() { 33 | _ = clientEnc.Close(context.TODO()) 34 | }() 35 | 36 | // look for a data key 37 | keyVault := strings.Split(keyVaultNamespace, ".") 38 | db := keyVault[0] 39 | coll := keyVault[1] 40 | var dataKey bson.M 41 | err = keyVaultClient. 42 | Database(db). 43 | Collection(coll). 44 | FindOne(context.TODO(), bson.M{"keyAltNames": keyAltName}). 45 | Decode(&dataKey) 46 | if err == mongo.ErrNoDocuments { 47 | // specify the master key information that will be used to 48 | // encrypt the data key(s) that will in turn be used to encrypt 49 | // fields, and create the data key 50 | dataKeyOpts := options.DataKey(). 51 | SetMasterKey(provider.DataKeyOpts()). 52 | SetKeyAltNames([]string{keyAltName}) 53 | dataKeyID, err := clientEnc.CreateDataKey(context.TODO(), provider.Name(), dataKeyOpts) 54 | if err != nil { 55 | return "", fmt.Errorf("create data key error %v", err) 56 | } 57 | 58 | return base64.StdEncoding.EncodeToString(dataKeyID.Data), nil 59 | } 60 | if err != nil { 61 | return "", fmt.Errorf("error encountered while attempting to find key") 62 | } 63 | 64 | return base64.StdEncoding.EncodeToString(dataKey["_id"].(primitive.Binary).Data), nil 65 | 66 | } 67 | -------------------------------------------------------------------------------- /go/csfle/encrypted_client.go: -------------------------------------------------------------------------------- 1 | package csfle 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/mongodb-university/csfle-guides/go/kms" 8 | "go.mongodb.org/mongo-driver/mongo" 9 | "go.mongodb.org/mongo-driver/mongo/options" 10 | ) 11 | 12 | func EncryptedClient(keyVaultNamespace, uri string, schemaMap map[string]interface{}, provider kms.Provider) (*mongo.Client, error) { 13 | // extra options that can be specified 14 | // see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#extraoptions 15 | extraOptions := map[string]interface{}{ 16 | // "mongocryptdURI":, defaults to "mongodb://localhost:27020" 17 | // "mongocryptdBypassSpawn":, defaults to false 18 | // "mongocryptdSpawnPath":, defaults to an empty string and spawns mongocryptd from the system path 19 | // "mongocryptdSpawnArgs":, defauls to ["--idleShutdownTimeoutSecs=60"] 20 | } 21 | autoEncryptionOpts := options.AutoEncryption(). 22 | SetKmsProviders(provider.Credentials()). 23 | SetKeyVaultNamespace(keyVaultNamespace). 24 | SetSchemaMap(schemaMap). 25 | SetExtraOptions(extraOptions) 26 | client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri).SetAutoEncryptionOptions(autoEncryptionOpts)) 27 | if err != nil { 28 | return nil, fmt.Errorf("Connect error for encrypted client: %v", err) 29 | } 30 | return client, nil 31 | } 32 | 33 | func InsertTestData(client *mongo.Client, doc interface{}, dbName, collName string) error { 34 | collection := client.Database(dbName).Collection(collName) 35 | 36 | if err := collection.Drop(context.TODO()); err != nil { 37 | return fmt.Errorf("Drop error: %v", err) 38 | } 39 | 40 | if _, err := collection.InsertOne(context.TODO(), doc); err != nil { 41 | return fmt.Errorf("InsertOne error: %v", err) 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /go/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mongodb-university/csfle-guides/go 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.34.28 // indirect 7 | github.com/fatih/structs v1.1.0 8 | github.com/joho/godotenv v1.3.0 9 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect 10 | github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc // indirect 11 | go.mongodb.org/mongo-driver v1.7.2 12 | ) 13 | -------------------------------------------------------------------------------- /go/go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= 3 | github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 8 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 9 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 10 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 11 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 12 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= 13 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= 14 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= 15 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 16 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 17 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= 18 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 19 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 20 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= 21 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= 22 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= 23 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= 24 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= 25 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= 26 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= 27 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= 28 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= 29 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 30 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 31 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 32 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 33 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= 34 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= 35 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= 36 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 37 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 38 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 39 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 41 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 42 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 43 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 44 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 45 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 46 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 47 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= 48 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= 49 | github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= 50 | github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 51 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 52 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 53 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 54 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 55 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 56 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 57 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 58 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 59 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= 60 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 61 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 62 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 63 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 64 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 65 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 66 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 67 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 68 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 69 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 70 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 71 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 72 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 73 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 74 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 75 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 76 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 77 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 78 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 79 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 80 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 81 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 82 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 83 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 84 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= 85 | github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= 86 | github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= 87 | github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= 88 | go.mongodb.org/mongo-driver v1.4.0-beta2.0.20201111202951-cdacb6473abf h1:/lmcWkMSu+Ab6eceQ5nXHbOpoZ66BrtCvgjV9/B8FY8= 89 | go.mongodb.org/mongo-driver v1.4.0-beta2.0.20201111202951-cdacb6473abf/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= 90 | go.mongodb.org/mongo-driver v1.7.2 h1:pFttQyIiJUHEn50YfZgC9ECjITMT44oiN36uArf/OFg= 91 | go.mongodb.org/mongo-driver v1.7.2/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= 92 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 93 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 94 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 95 | golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= 96 | golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 97 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 98 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 99 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= 100 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 101 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 102 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 103 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 104 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 105 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 106 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 107 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 108 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 109 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 110 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 111 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 112 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 113 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 114 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 115 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 116 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 117 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 118 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 119 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 120 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 121 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 122 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 123 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 124 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 125 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 126 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 127 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 128 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 129 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 130 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 131 | -------------------------------------------------------------------------------- /go/kms/provider.go: -------------------------------------------------------------------------------- 1 | package kms 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/fatih/structs" 8 | ) 9 | 10 | // GetCheckedEnv gets the specified environmental variable value and ensures 11 | // it isn't empty 12 | func GetCheckedEnv(env string) string { 13 | val := os.Getenv(env) 14 | if val == "" { 15 | log.Fatalf("You are attempting to use a KMS provider but not providing the required environmental value for %s. Please see the README", env) 16 | } 17 | return val 18 | } 19 | 20 | // Provider is a common interface for each KMS provider (aws, azure, gcp, local) 21 | type Provider interface { 22 | Name() string 23 | Credentials() map[string]map[string]interface{} 24 | DataKeyOpts() interface{} 25 | } 26 | 27 | // gcpKMSCredentials used to access this KMS provider 28 | // See https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#kmsproviders 29 | type gcpKMSCredentials struct { 30 | Email string `structs:"email"` 31 | PrivateKey string `structs:"privateKey"` 32 | Endpoint string `structs:"endpoint,omitempty"` // optional, defaults to oauth2.googleapis.com 33 | } 34 | 35 | // gcpKMSDataKeyOpts are the data key options used for this KMS provider. 36 | // See https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#datakeyopts 37 | type gcpKMSDataKeyOpts struct { 38 | ProjectID string `bson:"projectId"` 39 | Location string `bson:"location"` 40 | KeyRing string `bson:"keyRing"` 41 | KeyName string `bson:"keyName"` 42 | KeyVersion string `bson:"keyVersion,omitempty"` // optional, defaults to the key's primary version 43 | Endpoint string `bson:"endpoint,omitempty"` // optional, defaults to cloudkms.googleapis.com 44 | } 45 | 46 | // GCP holds the credentials and master key information to use this KMS 47 | // Get an instance of this with the kms.GCPProvider() method 48 | type GCP struct { 49 | credentials gcpKMSCredentials `structs:"gcp"` 50 | dataKeyOpts gcpKMSDataKeyOpts 51 | name string 52 | } 53 | 54 | // GCPProvider reads in the required environment variables for credentials and master key 55 | // location to use GCP as a KMS 56 | func GCPProvider() *GCP { 57 | // gcp kms privider credentials 58 | gcpEmail := GetCheckedEnv("FLE_GCP_EMAIL") 59 | gcpPrivateKey := GetCheckedEnv("FLE_GCP_PRIVATE_KEY") 60 | 61 | // gcp data key opts 62 | gcpProjectID := GetCheckedEnv("FLE_GCP_PROJ_ID") 63 | gcpLocation := GetCheckedEnv("FLE_GCP_LOCATION") 64 | gcpKeyRing := GetCheckedEnv("FLE_GCP_KEY_RING") 65 | gcpKeyName := GetCheckedEnv("FLE_GCP_KEY_NAME") 66 | 67 | return &GCP{ 68 | credentials: gcpKMSCredentials{ 69 | Email: gcpEmail, 70 | PrivateKey: gcpPrivateKey, 71 | }, 72 | dataKeyOpts: gcpKMSDataKeyOpts{ 73 | ProjectID: gcpProjectID, 74 | Location: gcpLocation, 75 | KeyRing: gcpKeyRing, 76 | KeyName: gcpKeyName, 77 | }, 78 | name: "gcp", 79 | } 80 | } 81 | 82 | // Name is the name of this provider 83 | func (g *GCP) Name() string { 84 | return g.name 85 | } 86 | 87 | // Credentials are the credentials for this provider returned in the format necessary 88 | // to immediately pass to the driver 89 | func (g *GCP) Credentials() map[string]map[string]interface{} { 90 | return map[string]map[string]interface{}{"gcp": structs.Map(g.credentials)} 91 | } 92 | 93 | // DataKeyOpts are the data key options for this provider returned in the format necessary 94 | // to immediately pass to the driver 95 | func (g *GCP) DataKeyOpts() interface{} { 96 | return g.dataKeyOpts 97 | } 98 | 99 | // Local holds the credentials and master key information to use this KMS 100 | // Get an instance of this with the kms.LocalProvider() method 101 | type Local struct { 102 | name string 103 | key []byte 104 | } 105 | 106 | // LocalProvider returns information for using the local KMS. 107 | // Not for production 108 | func LocalProvider(key []byte) *Local { 109 | return &Local{name: "local", key: key} 110 | } 111 | 112 | // Name is the name of this provider 113 | func (l *Local) Name() string { 114 | return l.name 115 | } 116 | 117 | // Credentials are the credentials for this provider returned in the format necessary 118 | // to immediately pass to the driver 119 | func (l *Local) Credentials() map[string]map[string]interface{} { 120 | return map[string]map[string]interface{}{ 121 | "local": { 122 | "key": l.key, 123 | }, 124 | } 125 | } 126 | 127 | // DataKeyOpts are the data key options for this provider returned in the format necessary 128 | // to immediately pass to the driver 129 | func (l *Local) DataKeyOpts() interface{} { 130 | return nil 131 | } 132 | 133 | // azureKMSCredentials used to access this KMS provider 134 | // See https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#kmsproviders 135 | type azureKMSCredentials struct { 136 | TenantID string `structs:"tenantId"` 137 | ClientID string `structs:"clientId"` 138 | ClientSecret string `structs:"clientSecret"` 139 | IdentityPlatformEndpoint string `structs:"identityPlatformEndpoint,omitempty"` // optional, defaults to login.microsoftonline.com 140 | } 141 | 142 | // The data key options used for this KMS provider. 143 | // See https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#datakeyopts 144 | type azureKMSDataKeyOpts struct { 145 | KeyVaultEndpoint string `bson:"keyVaultEndpoint"` 146 | KeyName string `bson:"keyName"` 147 | KeyVersion string `bson:"keyVersion,omitempty"` // optional, defaults to the key's primary version 148 | } 149 | 150 | // Azure holds the credentials and master key information to use this KMS 151 | // Get an instance of this with the kms.AzureProvider() method 152 | type Azure struct { 153 | credentials azureKMSCredentials 154 | dataKeyOpts azureKMSDataKeyOpts 155 | name string 156 | } 157 | 158 | // AzureProvider reads in the required environment variables for credentials and master key 159 | // location to use Azure as a KMS 160 | func AzureProvider() *Azure { 161 | // azure kms provider credentials 162 | azureTenantID := GetCheckedEnv("FLE_AZURE_TENANT_ID") 163 | azureClientID := GetCheckedEnv("FLE_AZURE_CLIENT_ID") 164 | azureClientSecret := GetCheckedEnv("FLE_AZURE_CLIENT_SECRET") 165 | 166 | // azure data key opts 167 | azureKeyVaultEndpoint := GetCheckedEnv("FLE_AZURE_KEYVAULT_ENDPOINT") 168 | azureKeyName := GetCheckedEnv("FLE_AZURE_KEY_NAME") 169 | 170 | return &Azure{ 171 | credentials: azureKMSCredentials{ 172 | TenantID: azureTenantID, 173 | ClientID: azureClientID, 174 | ClientSecret: azureClientSecret, 175 | }, 176 | dataKeyOpts: azureKMSDataKeyOpts{ 177 | KeyVaultEndpoint: azureKeyVaultEndpoint, 178 | KeyName: azureKeyName, 179 | }, 180 | name: "azure", 181 | } 182 | } 183 | 184 | // Name is the name of this provider 185 | func (a *Azure) Name() string { 186 | return a.name 187 | } 188 | 189 | // Credentials are the credentials for this provider returned in the format necessary 190 | // to immediately pass to the driver 191 | func (a *Azure) Credentials() map[string]map[string]interface{} { 192 | return map[string]map[string]interface{}{"azure": structs.Map(a.credentials)} 193 | } 194 | 195 | // DataKeyOpts are the data key options for this provider returned in the format necessary 196 | // to immediately pass to the driver 197 | func (a *Azure) DataKeyOpts() interface{} { 198 | return a.dataKeyOpts 199 | } 200 | 201 | // awsKMSCredentials used to access this KMS provider 202 | // See https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#kmsproviders 203 | 204 | type awsKMSCredentials struct { 205 | AccessKeyID string `structs:"accessKeyId"` 206 | SecretAccessKey string `structs:"secretAccessKey"` 207 | } 208 | 209 | // The data key options used for this KMS provider. 210 | // See https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#datakeyopts 211 | 212 | type awsKMSDataKeyOpts struct { 213 | Region string `bson:"region"` 214 | KeyARN string `bson:"key"` // the aws key arn 215 | Endpoint string `bson:"endpoint,omitempty"` // optional, defaults to kms..amazonaws.com 216 | } 217 | 218 | // AWS holds the credentials and master key information to use this KMS 219 | // Get an instance of this with the kms.AWSProvider() method 220 | type AWS struct { 221 | credentials awsKMSCredentials 222 | dataKeyOpts awsKMSDataKeyOpts 223 | name string 224 | } 225 | 226 | // AWSProvider reads in the required environment variables for credentials and master key 227 | // location to use AWS as a KMS 228 | func AWSProvider() *AWS { 229 | // aws kms provider credentials 230 | awsAccessKeyID := GetCheckedEnv("FLE_AWS_ACCESS_KEY") 231 | awsSecretAccessKey := GetCheckedEnv("FLE_AWS_SECRET_ACCESS_KEY") 232 | 233 | // aws data key opts 234 | awsKeyARN := GetCheckedEnv("FLE_AWS_KEY_ARN") 235 | awsKeyRegion := GetCheckedEnv("FLE_AWS_KEY_REGION") 236 | 237 | return &AWS{ 238 | credentials: awsKMSCredentials{ 239 | AccessKeyID: awsAccessKeyID, 240 | SecretAccessKey: awsSecretAccessKey, 241 | }, 242 | 243 | dataKeyOpts: awsKMSDataKeyOpts{ 244 | KeyARN: awsKeyARN, 245 | Region: awsKeyRegion, 246 | }, 247 | name: "aws", 248 | } 249 | } 250 | 251 | // Name is the name of this provider 252 | func (a *AWS) Name() string { 253 | return a.name 254 | } 255 | 256 | // Credentials are the credentials for this provider returned in the format necessary 257 | // to immediately pass to the driver 258 | func (a *AWS) Credentials() map[string]map[string]interface{} { 259 | return map[string]map[string]interface{}{"aws": structs.Map(a.credentials)} 260 | } 261 | 262 | // DataKeyOpts are the data key options for this provider returned in the format necessary 263 | // to immediately pass to the driver 264 | func (a *AWS) DataKeyOpts() interface{} { 265 | return a.dataKeyOpts 266 | } 267 | -------------------------------------------------------------------------------- /go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | 11 | "github.com/joho/godotenv" 12 | "github.com/mongodb-university/csfle-guides/go/csfle" 13 | "github.com/mongodb-university/csfle-guides/go/kms" 14 | "github.com/mongodb-university/csfle-guides/go/patient" 15 | "github.com/mongodb-university/csfle-guides/go/schema" 16 | "go.mongodb.org/mongo-driver/bson" 17 | "go.mongodb.org/mongo-driver/mongo" 18 | "go.mongodb.org/mongo-driver/mongo/options" 19 | ) 20 | 21 | const ( 22 | keyVaultNamespace = "encryption.__keyVault" 23 | uri = "mongodb://localhost:27017" 24 | dbName = "medicalRecords" 25 | collName = "patients" 26 | keyAltName = "demo-data-key" 27 | ) 28 | 29 | func localMasterKey() []byte { 30 | if _, err := os.Stat("master-key.txt"); err == nil { 31 | return getMasterKey() 32 | } 33 | key := make([]byte, 96) 34 | if _, err := rand.Read(key); err != nil { 35 | log.Fatalf("Unable to create a random 96 byte data key: %v", err) 36 | } 37 | if err := ioutil.WriteFile("master-key.txt", key, 0644); err != nil { 38 | log.Fatalf("Unable to write key to file: %v", err) 39 | } 40 | return key 41 | } 42 | 43 | func getMasterKey() []byte { 44 | key, err := ioutil.ReadFile("master-key.txt") 45 | if err != nil { 46 | log.Fatalf("Could not read the key from master-key.txt: %v", err) 47 | } 48 | return key 49 | } 50 | 51 | func main() { 52 | err := godotenv.Load() 53 | 54 | // preferredProvider := kms.AWSProvider() 55 | // preferredProvider := kms.AzureProvider() 56 | // preferredProvider := kms.GCPProvider() 57 | preferredProvider := kms.LocalProvider(localMasterKey()) 58 | 59 | // getting the base64 representation of a new data key 60 | dataKeyBase64, err := csfle.GetDataKey(keyVaultNamespace, uri, keyAltName, preferredProvider) 61 | if err != nil { 62 | log.Fatalf("problem during data key creation: %v", err) 63 | } 64 | 65 | // configuring our jsonSchema for automatic helpers 66 | // the driver only uses this for encryption information, 67 | // not to enforce schema constraints 68 | 69 | s, err := schema.CreateJSONSchema(dataKeyBase64) 70 | if err != nil { 71 | log.Panic(err) 72 | } 73 | schemaMap := map[string]interface{}{ 74 | dbName + "." + collName: s, 75 | } 76 | // get a client with auto encryption using the new schema 77 | eclient, err := csfle.EncryptedClient(keyVaultNamespace, uri, schemaMap, preferredProvider) 78 | if err != nil { 79 | log.Panic(err) 80 | } 81 | // the auto encrypting client is ready for work 82 | defer func() { 83 | _ = eclient.Disconnect(context.TODO()) 84 | }() 85 | doc := patient.GetExamplePatient() 86 | // drop the collection and insert a new document with the encrypted client 87 | if err := csfle.InsertTestData(eclient, doc, dbName, collName); err != nil { 88 | log.Panic(err) 89 | } 90 | 91 | // creating an unencrypted client for a read operation to verify encryption 92 | uclient, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri)) 93 | if err != nil { 94 | log.Panicf("unencrypted client connect error %v", err) 95 | } 96 | defer func() { 97 | _ = uclient.Disconnect(context.TODO()) 98 | }() 99 | 100 | FindDocument(eclient, dbName, collName) 101 | FindDocument(uclient, dbName, collName) 102 | 103 | } 104 | 105 | // FindDocument is just a wrapper around FindOne that accepts a client to perform 106 | // the find operation with to illustrate CSFLE 107 | func FindDocument(client *mongo.Client, dbName, collName string) { 108 | collection := client.Database(dbName).Collection(collName) 109 | res, err := collection.FindOne(context.TODO(), bson.D{}).DecodeBytes() 110 | if err != nil { 111 | log.Fatalf("FindOne error: %v", err) 112 | } 113 | fmt.Println(res) 114 | } 115 | -------------------------------------------------------------------------------- /go/master-key.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb-university/csfle-guides/fdc3cdfc4890b2bc3e8fd4f40a54a013b7b93a53/go/master-key.txt -------------------------------------------------------------------------------- /go/patient/patient.go: -------------------------------------------------------------------------------- 1 | package patient 2 | 3 | type medicalRecord struct { 4 | Weight int `bson:"weight"` 5 | BloodPressure string `bson:"bloodPressure"` 6 | } 7 | 8 | type insurance struct { 9 | Provider string `bson:"provider"` 10 | PolicyNumber int `bson:"policyNumber"` 11 | } 12 | 13 | type Patient struct { 14 | Name string `bson:"name"` 15 | SSN int `bson:"ssn"` 16 | BloodType string `bson:"bloodType"` 17 | medicalRecords []medicalRecord `bson:"medicalRecords"` 18 | insurance insurance `bson:"insurance"` 19 | } 20 | 21 | func GetExamplePatient() Patient { 22 | 23 | return Patient{ 24 | Name: "Jon Doe", 25 | SSN: 241014209, 26 | BloodType: "AB+", 27 | medicalRecords: []medicalRecord{{ 28 | Weight: 180, 29 | BloodPressure: "120/80", 30 | }}, 31 | insurance: insurance{ 32 | Provider: "MaestCare", 33 | PolicyNumber: 123142, 34 | }, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /go/schema/json_schema.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "fmt" 5 | 6 | "go.mongodb.org/mongo-driver/bson" 7 | ) 8 | 9 | func CreateJSONSchema(dataKeyBase64 string) (bson.Raw, error) { 10 | jsc := `{ 11 | "bsonType": "object", 12 | "encryptMetadata": { 13 | "keyId": [ 14 | { 15 | "$binary": { 16 | "base64": "%s", 17 | "subType": "04" 18 | } 19 | } 20 | ] 21 | }, 22 | "properties": { 23 | "insurance": { 24 | "bsonType": "object", 25 | "properties": { 26 | "policyNumber": { 27 | "encrypt": { 28 | "bsonType": "int", 29 | "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" 30 | } 31 | } 32 | } 33 | }, 34 | "medicalRecords": { 35 | "encrypt": { 36 | "bsonType": "array", 37 | "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" 38 | } 39 | }, 40 | "bloodType": { 41 | "encrypt": { 42 | "bsonType": "string", 43 | "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" 44 | } 45 | }, 46 | "ssn": { 47 | "encrypt": { 48 | "bsonType": "int", 49 | "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" 50 | } 51 | } 52 | } 53 | }` 54 | schema := fmt.Sprintf(jsc, dataKeyBase64) 55 | var schemaDoc bson.Raw 56 | if err := bson.UnmarshalExtJSON([]byte(schema), true, &schemaDoc); err != nil { 57 | return nil, fmt.Errorf("UnmarshalExtJSON error: %v", err) 58 | } 59 | return schemaDoc, nil 60 | } 61 | -------------------------------------------------------------------------------- /java/.envrc: -------------------------------------------------------------------------------- 1 | # This is an arbitrary password for the KeyStore you generate when you run configure_certs.sh 2 | export KEYSTORE_PASSWORD="bithere" 3 | # You must enter the password for your global Java TrustStore 4 | export TRUSTSTORE_PASSWORD="" 5 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | drivers-evergreen-tools/*/* 2 | .generated_certs/* 3 | .mvn/maven.config 4 | 5 | ### JetBrains ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff: 10 | .idea/workspace.xml 11 | .idea/tasks.xml 12 | .idea/dictionaries 13 | .idea/vcs.xml 14 | .idea/jsLibraryMappings.xml 15 | 16 | # Sensitive or high-churn files: 17 | .idea/dataSources.ids 18 | .idea/dataSources.xml 19 | .idea/dataSources.local.xml 20 | .idea/sqlDataSources.xml 21 | .idea/dynamic.xml 22 | .idea/uiDesigner.xml 23 | 24 | # Gradle: 25 | .idea/gradle.xml 26 | .idea/libraries 27 | 28 | # Mongo Explorer plugin: 29 | .idea/mongoSettings.xml 30 | 31 | ## File-based project format: 32 | *.iws 33 | 34 | ## Plugin-specific files: 35 | 36 | # IntelliJ 37 | /out/ 38 | 39 | # mpeltonen/sbt-idea plugin 40 | .idea_modules/ 41 | 42 | # JIRA plugin 43 | atlassian-ide-plugin.xml 44 | 45 | # Crashlytics plugin (for Android Studio and IntelliJ) 46 | com_crashlytics_export_strings.xml 47 | crashlytics.properties 48 | crashlytics-build.properties 49 | fabric.properties 50 | 51 | ### JetBrains Patch ### 52 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 53 | 54 | # *.iml 55 | # modules.xml 56 | # .idea/misc.xml 57 | # *.ipr 58 | -------------------------------------------------------------------------------- /java/README.md: -------------------------------------------------------------------------------- 1 | # Java CSFLE Example 2 | 3 | ## Steps 4 | 5 | 1. Clone this repository: 6 | 7 | ``` 8 | git clone https://github.com/mongodb-university/csfle-guides.git 9 | ``` 10 | 11 | Work from the repository directory for the remainder of these 12 | instructions. 13 | 14 | 2. Start a `mongod` instance (Enterprise version >= 4.2) running on port 27017 15 | 3. Set up your build environment for the Java (>= 1.8) project. The following 16 | build files are located in the project root: 17 | 18 | - `pom.xml` for [Maven](https://maven.apache.org/) 19 | - `build.gradle` for [Gradle](https://gradle.org/) 20 | 21 | 4. Make sure you have the `master-key.txt` file in the root of your 22 | execution environment. This is a 96-byte cryptographically-secure generated 23 | master encryption key required to run this example project. To generate your 24 | own master key or use a KMS, refer to the [CSFLE Use Case 25 | Guide](https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-guide/). 26 | 27 | The settings for each supported KMS are included in 28 | `DataEncryptionKeyCreator.java` and `InsertDataWithEncryptedFields.java`. 29 | If you are using a cloud KMS provider, uncomment and assign your KMS 30 | provider settings. See 31 | [Use a KMS to Store the Master Key Guide](https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-local-key-to-kms) 32 | for more information on how to set up a master key and data encryption 33 | key with on of the supported KMS providers. 34 | 35 | 5. Make sure the `mongocryptd` path is correct in the `CSFLEHelpers.java` 36 | class. 37 | 38 | 6. Run the `main` method in `DataEncryptionKeyCreator.java` to insert 39 | a new data key in the **encryption.\_\_keyVault** collection. Copy the key id 40 | that is printed on the console. 41 | 42 | 7. Update the `keyId` variable in `InsertDataWithEncryptedFields.java` 43 | with the value from the previous step. 44 | 45 | ```java 46 | String keyId = ""; // paste the key generated by DataEncryptionKeyCreator here 47 | ``` 48 | 49 | 8. Run the `main` method in `InsertDataWithEncryptedFields.java`. This 50 | executes the following: 51 | 52 | - Inserts (using upsert) a sample document with encrypted fields 53 | - Retrieves the sample document with a CSFLE-enabled client to view the 54 | decrypted fields. 55 | - Attempts to retrieve the sample document with a normal client on an 56 | encrypted field, and is unsuccessful. 57 | - Retrieves the sample document with a normal client on a normal 58 | field. The retrieved document contains encrypted fields that are not 59 | human-readable. 60 | 61 | 9. Update the code to insert a document with the regular client. What happens? 62 | 63 | ## KMIP 64 | 65 | **KMIP support in this guide relies on unstable dependencies**. 66 | 67 | _This guide uses the [`direnv`](https://direnv.net/) tool to manage environment variables._ 68 | 69 | 1. Update the `TRUSTSTORE_PASSWORD` environment variable in your `.envrc` file to your global Java TrustStore password. 70 | 71 | 2. Run `direnv allow` to add the `TRUSTSTORE_PASSWORD` and `KEYSTORE_PASSWORD` environment variables to your run-time environment. 72 | 73 | 3. Run `source ./configure_certs.sh` to configure certificates and 74 | start your unstable KMIP test server. 75 | 76 | | ⚠️ | Do not use the provided KMIP server in a production environment. | 77 | | :-: | :--------------------------------------------------------------- | 78 | 79 | 4. Execute the following command from the `csfle-examples/java` directory to create a data encryption key: 80 | 81 | ``` 82 | mvn compile exec:java -Dexec.mainClass="com.mongodb.csfle.DataEncryptionKeyCreator" 83 | ``` 84 | 85 | 5. Enter your key as the value of the 86 | `keyId` local variable in the main method 87 | of the `InsertDataWithEncryptedFields` class. 88 | 89 | 6. Run the following command from the `csfle-examples/java` directory to insert an encrypted document into MongoDB: 90 | 91 | ``` 92 | mvn compile exec:java -Dexec.mainClass="com.mongodb.csfle.InsertDataWithEncryptedFields" 93 | ``` 94 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | } 4 | 5 | group = 'com.mongodb.csfle' 6 | 7 | version = '0.1-SNAPSHOT' 8 | 9 | repositories { 10 | jcenter() 11 | } 12 | 13 | dependencies { 14 | compile( 15 | [ 16 | 'org.mongodb:mongodb-crypt:1.1.0-beta1', 17 | 'org.mongodb:mongodb-driver-sync:4.1.1' 18 | ] 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /java/configure_certs.sh: -------------------------------------------------------------------------------- 1 | KMIP_PATH="../.kmip" 2 | 3 | # configure certificates for java 4 | mkdir .generated_certs 5 | openssl pkcs12 -CAfile "$KMIP_PATH"/certs/ca.pem -export -in "$KMIP_PATH"/certs/client.pem -out .generated_certs/client.pkc -password pass:${KEYSTORE_PASSWORD} 6 | cp ${JAVA_HOME}/lib/security/cacerts .generated_certs/mongo-truststore 7 | ${JAVA_HOME}/bin/keytool -importcert -trustcacerts -file "$KMIP_PATH"/certs/ca.pem -keystore .generated_certs/mongo-truststore -storepass ${KEYSTORE_PASSWORD} -storetype JKS -noprompt 8 | 9 | # specify password in maven config file 10 | mkdir .mvn 11 | cp maven.config.tmpl .mvn/maven.config 12 | sed -i '' -e "s/REPLACE-WITH-KEYSTORE-PASSWORD/$KEYSTORE_PASSWORD/g" .mvn/maven.config 13 | sed -i '' -e "s/REPLACE-WITH-TRUSTSTORE-PASSWORD/$TRUSTSTORE_PASSWORD/g" .mvn/maven.config 14 | 15 | # start the kmip server 16 | echo "Starting the KMIP Server..." 17 | cd "$KMIP_PATH"/kmip_server/ 18 | . ./activate_venv.sh 19 | ./kmstlsvenv/bin/python3 -u kms_kmip_server.py 20 | -------------------------------------------------------------------------------- /java/master-key.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb-university/csfle-guides/fdc3cdfc4890b2bc3e8fd4f40a54a013b7b93a53/java/master-key.txt -------------------------------------------------------------------------------- /java/maven.config.tmpl: -------------------------------------------------------------------------------- 1 | -Djavax.net.ssl.enabled=true 2 | -Djavax.net.ssl.keyStoreType=pkcs12 3 | -Djavax.net.ssl.keyStore=./.generated_certs/client.pkc 4 | -Djavax.net.ssl.keyStorePassword=REPLACE-WITH-KEYSTORE-PASSWORD 5 | -Djavax.net.ssl.trustStoreType=jks 6 | -Djavax.net.ssl.trustStore=./.generated_certs/mongo-truststore 7 | -Djavax.net.ssl.trustStorePassword=REPLACE-WITH-TRUSTSTORE-PASSWORD 8 | -------------------------------------------------------------------------------- /java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | Java-CSFLE-guide 4 | Java-CSFLE-guide 5 | 1.0-SNAPSHOT 6 | 7 | 8 | 1.8 9 | 1.8 10 | 11 | 12 | 13 | 14 | 15 | org.mongodb 16 | mongodb-driver-sync 17 | 4.4.0 18 | 19 | 20 | 21 | org.mongodb 22 | mongodb-crypt 23 | 1.3.0 24 | 25 | 26 | 27 | 28 | 29 | ${project.artifactId} 30 | 31 | 32 | -------------------------------------------------------------------------------- /java/src/main/java/com/mongodb/csfle/DataEncryptionKeyCreator.java: -------------------------------------------------------------------------------- 1 | package com.mongodb.csfle; 2 | /* 3 | * Copyright 2008-present MongoDB, Inc. 4 | 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | import com.mongodb.csfle.util.CSFLEHelpers;; 24 | 25 | /* 26 | * - Reads master key from file "master-key.txt" in root directory of project, or creates one on a KMS 27 | * - Locates existing local encryption key from encryption.__keyVault collection, or from a KMS 28 | * - Prints base 64-encoded value of the data encryption key 29 | */ 30 | public class DataEncryptionKeyCreator { 31 | 32 | public static void main(String[] args) throws Exception { 33 | String connectionString = "mongodb://localhost:27017"; 34 | String keyDb = "encryption"; 35 | String keyColl = "__keyVault"; 36 | String keyVaultCollection = String.join(".", keyDb, keyColl); 37 | String keyAltName = "demo-data-key"; 38 | 39 | Map masterKeyProperties = new HashMap<>(); 40 | Map> kmsProviderProperties = new HashMap<>(); 41 | 42 | /* START: Local master key block */ 43 | String kmsProvider = "local"; 44 | 45 | masterKeyProperties.put("provider", kmsProvider); 46 | byte[] masterKey = CSFLEHelpers.readMasterKey("./master-key.txt"); 47 | masterKeyProperties.put("key", masterKey); 48 | 49 | Map localMasterKey = new HashMap<>(); 50 | localMasterKey.put("key", masterKey); 51 | kmsProviderProperties.put(kmsProvider, localMasterKey); 52 | /* END: Local master key block */ 53 | 54 | /* 55 | * AWS KMS 56 | * Uncomment this block to use your AWS KMS provider key 57 | String kmsProvider = "aws"; 58 | masterKeyProperties.put("provider", kmsProvider); 59 | masterKeyProperties.put("key", ""); 60 | masterKeyProperties.put("region", ""); 61 | masterKeyProperties.put("endpoint", ""); // optional 62 | 63 | Map providerDetails = new HashMap<>(); 64 | providerDetails.put("accessKeyId", ""); 65 | providerDetails.put("secretAccessKey",""); 66 | kmsProviders.put(kmsProvider, providerDetails); 67 | */ 68 | 69 | /* 70 | * Azure KMS 71 | * Uncomment this block to use your Azure KMS provider key 72 | String kmsProvider = "azure"; 73 | masterKeyProperties.put("provider", kmsProvider); 74 | masterKeyProperties.put("keyName": ""); 75 | masterKeyProperties.put("keyVersion": ""); 76 | masterKeyProperties.put("keyVaultEndpoint": " providerDetails = new HashMap<>(); 79 | providerDetails.put("tenantId", ""); 80 | providerDetails.put("clientId", ""); 81 | providerDetails.put("clientSecret", ""); 82 | providerDetails.put("identityPlatformEndpoint", ""); // optional 83 | kmsProviders.put(kmsProvider, providerDetails); 84 | */ 85 | 86 | /* 87 | * GCP KMS 88 | * Uncomment this block to use your GCP KMS provider key 89 | String kmsProvider = "gcp"; 90 | masterKeyProperties.put("provider", kmsProvider); 91 | masterKeyProperties.put("projectId", ""); 92 | masterKeyProperties.put("location", ""); 93 | masterKeyProperties.put("keyRing", ""); 94 | masterKeyProperties.put("keyName", ""); 95 | masterKeyProperties.put("keyVersion", ""); // optional 96 | 97 | Map providerDetails = new HashMap<>(); 98 | providerDetails.put("email", ""); 99 | providerDetails.put("privateKey",""); 100 | providerDetails.put("endpoint", ""); // optional 101 | kmsProviders.put(kmsProvider, providerDetails); 102 | */ 103 | 104 | /* 105 | * KMIP KMS 106 | * Uncomment this block to use your KMIP KMS provider key 107 | String kmsProvider = "kmip"; 108 | masterKeyProperties.put("provider", "kmip"); 109 | masterKeyProperties.put("keyId", "1"); 110 | 111 | Map providerDetails = new HashMap<>(); 112 | providerDetails.put("endpoint", "localhost:5698"); 113 | kmsProviderProperties.put(kmsProvider, providerDetails); 114 | */ 115 | 116 | // Ensure index exists on key vault 117 | CSFLEHelpers.createKeyVaultIndex(connectionString, keyDb, keyColl); 118 | 119 | // Find or insert a new data data key 120 | String encryptionKey = CSFLEHelpers.findDataEncryptionKey(connectionString, keyAltName, keyDb, keyColl); 121 | 122 | if (encryptionKey != null) { 123 | // Print the key 124 | System.out.println("Retrieved your existing key. Copy the key below and paste it into InsertDataWithEncryptedFields.java\n" + encryptionKey); 125 | System.exit(0); 126 | } 127 | 128 | encryptionKey = CSFLEHelpers.createDataEncryptionKey(connectionString, masterKeyProperties, kmsProviderProperties, keyVaultCollection, keyAltName); 129 | 130 | System.out.println("Congratulations on creating a new data encryption key! Copy the key below and paste it into InsertDataWithEncryptedfields.java\n" + encryptionKey); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /java/src/main/java/com/mongodb/csfle/InsertDataWithEncryptedFields.java: -------------------------------------------------------------------------------- 1 | package com.mongodb.csfle; 2 | /* 3 | * Copyright 2008-present MongoDB, Inc. 4 | 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | 20 | import static com.mongodb.client.model.Filters.eq; 21 | 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | import org.bson.BsonString; 26 | import org.bson.Document; 27 | import org.bson.conversions.Bson; 28 | 29 | import com.mongodb.ConnectionString; 30 | import com.mongodb.client.MongoClient; 31 | import com.mongodb.client.MongoClients; 32 | import com.mongodb.client.MongoCollection; 33 | import com.mongodb.client.model.UpdateOptions; 34 | import com.mongodb.csfle.util.CSFLEHelpers; 35 | 36 | /* 37 | * - Reads master key from file "master-key.txt" in root directory of project 38 | * - Creates a JSON schema for a specified collection to enable automatic encryption 39 | * - Creates an encrypted client and upserts a single document 40 | * - Finds the upserted document with the encrypted client using an encrypted field 41 | * - Attempts to find the upserted document with the normal client using an encrypted field 42 | * - Finds the upserted document with the normal client using a non-encrypted field 43 | */ 44 | public class InsertDataWithEncryptedFields { 45 | 46 | public static void main(String[] args) throws Exception { 47 | String connectionString = "mongodb://localhost:27017"; 48 | String keyDb = "encryption"; 49 | String keyColl = "__keyVault"; 50 | String keyVaultCollection = String.join(".", keyDb, keyColl); 51 | String recordsDb = "medicalRecords"; 52 | String recordsColl = "patients"; 53 | Bson ssnQuery = eq("ssn", CSFLEHelpers.SAMPLE_SSN_VALUE); 54 | String keyId = ""; // paste the key generated by DataEncryptionKeyCreator here 55 | 56 | Map> kmsProviders = new HashMap<>(); 57 | Map providerDetails = new HashMap<>(); 58 | 59 | /* START: Local master key block */ 60 | String kmsProvider = "local"; 61 | // Read the local master key from the provided file 62 | byte[] masterKey = CSFLEHelpers.readMasterKey("./master-key.txt"); 63 | 64 | providerDetails.put("key", masterKey); 65 | 66 | kmsProviders.put("local", providerDetails); 67 | /* END: Local master key block */ 68 | 69 | /* 70 | * AWS KMS 71 | * Uncomment this block to use your AWS KMS provider key 72 | String kmsProvider = "aws"; 73 | providerDetails.put("accessKeyId", ""); 74 | providerDetails.put("secretAccessKey", ""); 75 | kmsProviders.put(kmsProvider, providerDetails); 76 | */ 77 | 78 | /* Azure KMS 79 | * Uncomment this block to use your Azure KMS provider key 80 | String kmsProvider = "azure"; 81 | Map providerDetails = new HashMap<>(); 82 | providerDetails.put("tenantId", ""); 83 | providerDetails.put("clientId", ""); 84 | providerDetails.put("clientSecret", ""); 85 | providerDetails.put("identityPlatformEndpoint", ""); // optional 86 | kmsProviders.put(kmsProvider, providerDetails); 87 | */ 88 | 89 | /* 90 | * GCP KMS 91 | * Uncomment this block to use your GCP KMS provider key 92 | String kmsProvider = "gcp"; 93 | Map providerDetails = new HashMap<>(); 94 | providerDetails.put("email", ""); 95 | providerDetails.put("privateKey", ""); 96 | providerDetails.put("endpoint", ""); //optional 97 | kmsProviders.put(kmsProvider, providerDetails); 98 | */ 99 | 100 | /* 101 | * KMIP KMS 102 | * Uncomment this block to use your KMIP KMS provider key 103 | String kmsProvider = "kmip"; 104 | providerDetails.put("endpoint", "localhost:5698"); 105 | kmsProviders.put(kmsProvider, providerDetails); 106 | */ 107 | 108 | 109 | // Construct a JSON Schema 110 | Document schema = CSFLEHelpers.createJSONSchema(keyId); 111 | System.out.println("Schema: " + schema.toJson()); 112 | 113 | // Construct an encrypted client 114 | try (MongoClient encryptedClient = 115 | CSFLEHelpers.createEncryptedClient(connectionString, 116 | kmsProvider, 117 | kmsProviders, 118 | keyVaultCollection, schema, recordsDb, recordsColl)) { 119 | 120 | // Insert a document into the collection 121 | MongoCollection collection = encryptedClient.getDatabase(recordsDb).getCollection(recordsColl); 122 | 123 | collection.updateOne(ssnQuery, new Document("$set", CSFLEHelpers.SAMPLE_DOC), new UpdateOptions().upsert(true)); 124 | System.out.println("Successfully upserted the sample document!"); 125 | 126 | // Query SSN field with encrypted client 127 | Document result = collection.find(ssnQuery).first(); 128 | if (result == null) { 129 | throw new Exception("No query results were returned, please verify the document was inserted"); 130 | } 131 | System.out.println("Encrypted client query by the SSN (deterministically-encrypted) field:\n" + result.toJson()); 132 | } 133 | 134 | // Query SSN field with normal client without encryption 135 | try (MongoClient normalMongoClient = MongoClients.create(new ConnectionString(connectionString))) { 136 | MongoCollection normalCollection = normalMongoClient.getDatabase(recordsDb).getCollection(recordsColl); 137 | 138 | Document normalClientResult = normalCollection.find(ssnQuery).first(); 139 | assert(normalClientResult == null) : "Check your collection documents for unencrypted SSN fields:\n" + normalClientResult; 140 | 141 | // Query name (non-encrypted) field with normal client without encryption 142 | Bson nameQuery = eq("name", CSFLEHelpers.SAMPLE_NAME_VALUE); 143 | Document normalClientNameResult = normalCollection.find(nameQuery).first(); 144 | assert(normalClientNameResult != null) : "Check your collection to ensure a document with the matching name field exists."; 145 | System.out.println("Query by name returned the following document:\n " + normalClientNameResult); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /java/src/main/java/com/mongodb/csfle/util/CSFLEHelpers.java: -------------------------------------------------------------------------------- 1 | package com.mongodb.csfle.util; 2 | /* 3 | * Copyright 2008-present MongoDB, Inc. 4 | 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | 20 | 21 | import java.io.FileInputStream; 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.Base64; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | import org.bson.BsonBinary; 30 | import org.bson.BsonDocument; 31 | import org.bson.BsonString; 32 | import org.bson.Document; 33 | import org.bson.conversions.Bson; 34 | 35 | import com.mongodb.AutoEncryptionSettings; 36 | import com.mongodb.ClientEncryptionSettings; 37 | import com.mongodb.ConnectionString; 38 | import com.mongodb.MongoClientSettings; 39 | import com.mongodb.client.MongoClient; 40 | import com.mongodb.client.MongoClients; 41 | import com.mongodb.client.MongoCollection; 42 | import com.mongodb.client.model.Filters; 43 | import com.mongodb.client.model.IndexOptions; 44 | import com.mongodb.client.model.vault.DataKeyOptions; 45 | import com.mongodb.client.vault.ClientEncryption; 46 | import com.mongodb.client.vault.ClientEncryptions; 47 | 48 | /* 49 | * Helper methods and sample data for this companion project. 50 | */ 51 | public class CSFLEHelpers { 52 | 53 | // Sample data 54 | public static final String SAMPLE_NAME_VALUE = "John Doe"; 55 | public static final Integer SAMPLE_SSN_VALUE = 145014000; 56 | 57 | public static final Document SAMPLE_DOC = new Document() 58 | .append("name", SAMPLE_NAME_VALUE) 59 | .append("ssn", SAMPLE_SSN_VALUE) 60 | .append("bloodType", "AB-") 61 | .append("keyAltNameField", "bloodTypeKey") 62 | .append("medicalRecords", Arrays.asList( 63 | new Document().append("weight", "180"), 64 | new Document().append("bloodPressure", "120/80"))) 65 | .append("insurance", new Document() 66 | .append("policyNumber", 123142) 67 | .append("provider", "MaestCare")); 68 | 69 | // Reads the 96-byte local master key 70 | public static byte[] readMasterKey(String filePath) throws Exception { 71 | int numBytes = 96; 72 | byte[] fileBytes = new byte[numBytes]; 73 | 74 | try (FileInputStream fis = new FileInputStream(filePath)) { 75 | if (fis.read(fileBytes) < numBytes) 76 | throw new Exception("Expected to read 96 bytes from file"); 77 | } 78 | return fileBytes; 79 | } 80 | 81 | // JSON Schema helpers 82 | private static Document buildEncryptedField(String bsonType, Boolean isDeterministic) { 83 | String DETERMINISTIC_ENCRYPTION_TYPE = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"; 84 | String RANDOM_ENCRYPTION_TYPE = "AEAD_AES_256_CBC_HMAC_SHA_512-Random"; 85 | 86 | Document fieldDetails = new Document() 87 | .append("bsonType", bsonType) 88 | .append("algorithm", 89 | (isDeterministic) ? DETERMINISTIC_ENCRYPTION_TYPE : RANDOM_ENCRYPTION_TYPE); 90 | 91 | return new Document().append("encrypt", fieldDetails); 92 | } 93 | 94 | private static Document createEncryptMetadataSchema(String keyId) { 95 | List keyIds = new ArrayList<>(); 96 | keyIds.add(new Document() 97 | .append("$binary", new Document() 98 | .append("base64", keyId) 99 | .append("subType", "04"))); 100 | return new Document().append("keyId", keyIds); 101 | } 102 | 103 | public static Document createJSONSchema(String keyId) throws IllegalArgumentException { 104 | if (keyId.isEmpty()) { 105 | throw new IllegalArgumentException("keyId must contain your base64 encryption key id."); 106 | } 107 | return new Document().append("bsonType", "object").append("encryptMetadata", createEncryptMetadataSchema(keyId)) 108 | .append("properties", new Document() 109 | .append("ssn", buildEncryptedField("int", true)) 110 | .append("bloodType", buildEncryptedField("string", false)) 111 | .append("medicalRecords", buildEncryptedField("array", false)) 112 | .append("insurance", new Document() 113 | .append("bsonType", "object") 114 | .append("properties", 115 | new Document().append("policyNumber", buildEncryptedField("int", true))))); 116 | } 117 | 118 | // Creates Normal Client 119 | private static MongoClient createMongoClient(String connectionString) { 120 | return MongoClients.create(connectionString); 121 | } 122 | 123 | 124 | // Creates Encrypted Client which performs automatic encryption and decryption of fields 125 | public static MongoClient createEncryptedClient(String connectionString, String kmsProvider, 126 | Map> kmsProviders, 127 | String keyVaultCollection, Document schema, String dataDb, String dataColl) { 128 | // You may need to update the following variable to point to your mongocryptd binary 129 | String mongocryptdPath = "/usr/local/bin/mongocryptd"; 130 | 131 | String recordsNamespace = dataDb + "." + dataColl; 132 | 133 | Map schemaMap = new HashMap<>(); 134 | schemaMap.put(recordsNamespace, BsonDocument.parse(schema.toJson())); 135 | 136 | 137 | Map extraOpts = new HashMap<>(); 138 | extraOpts.put("mongocryptdSpawnPath", mongocryptdPath); 139 | // uncomment the following line if you are running mongocryptd manually 140 | // extraOpts.put("mongocryptdBypassSpawn", true); 141 | 142 | AutoEncryptionSettings autoEncryptionSettings = AutoEncryptionSettings.builder() 143 | .keyVaultNamespace(keyVaultCollection) 144 | .kmsProviders(kmsProviders) 145 | .extraOptions(extraOpts) 146 | .schemaMap(schemaMap) 147 | .build(); 148 | 149 | MongoClientSettings clientSettings = MongoClientSettings.builder() 150 | .applyConnectionString(new ConnectionString(connectionString)) 151 | .autoEncryptionSettings(autoEncryptionSettings) 152 | .build(); 153 | 154 | return MongoClients.create(clientSettings); 155 | } 156 | 157 | // Returns existing data encryption key 158 | public static String findDataEncryptionKey(String connectionString, String keyAltName, String keyDb, String keyColl) { 159 | try (MongoClient mongoClient = createMongoClient(connectionString)) { 160 | Document query = new Document("keyAltNames", keyAltName); 161 | MongoCollection collection = mongoClient.getDatabase(keyDb).getCollection(keyColl); 162 | BsonDocument doc = collection 163 | .withDocumentClass(BsonDocument.class) 164 | .find(query) 165 | .first(); 166 | 167 | if (doc != null) { 168 | return Base64.getEncoder().encodeToString(doc.getBinary("_id").getData()); 169 | } 170 | return null; 171 | } 172 | } 173 | 174 | // Creates index for keyAltNames in the specified key collection 175 | public static void createKeyVaultIndex(String connectionString, String keyDb, String keyColl) { 176 | try (MongoClient mongoClient = createMongoClient(connectionString)) { 177 | MongoCollection collection = mongoClient.getDatabase(keyDb).getCollection(keyColl); 178 | 179 | Bson filterExpr = Filters.exists("keyAltNames", true); 180 | IndexOptions indexOptions = new IndexOptions().unique(true).partialFilterExpression(filterExpr); 181 | 182 | collection.createIndex(new Document("keyAltNames", 1), indexOptions); 183 | } 184 | } 185 | 186 | // Create data encryption key in the specified key collection 187 | // Call only after checking whether a data encryption key with same keyAltName exists 188 | public static String createDataEncryptionKey( 189 | String connectionString, 190 | Map masterKeyProperties, 191 | Map> kmsProviderProperties, 192 | String keyVaultCollection, 193 | String keyAltName) { 194 | 195 | List keyAltNames = new ArrayList<>(); 196 | keyAltNames.add(keyAltName); 197 | 198 | try (ClientEncryption keyVault = createKeyVault(connectionString, kmsProviderProperties, keyVaultCollection)) { 199 | 200 | BsonBinary dataKeyId = keyVault.createDataKey(masterKeyProperties.get("provider").toString(), 201 | createDataKeyOptions(masterKeyProperties).keyAltNames(keyAltNames)); 202 | 203 | return Base64.getEncoder().encodeToString(dataKeyId.getData()); 204 | } 205 | } 206 | 207 | private static DataKeyOptions createDataKeyOptions(Map masterKeyProperties) { 208 | 209 | BsonDocument doc = new BsonDocument(); 210 | for (Map.Entry prop : masterKeyProperties.entrySet()) { 211 | doc.append(prop.getKey(), new BsonString(prop.getValue().toString())); 212 | } 213 | 214 | return new DataKeyOptions().masterKey(doc); 215 | } 216 | 217 | // Creates KeyVault which allows you to create a key as well as encrypt and decrypt fields 218 | private static ClientEncryption createKeyVault(String connectionString, 219 | Map> kmsProviders, 220 | String keyVaultCollection) { 221 | 222 | ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() 223 | .keyVaultMongoClientSettings(MongoClientSettings.builder() 224 | .applyConnectionString(new ConnectionString(connectionString)) 225 | .build()) 226 | .keyVaultNamespace(keyVaultCollection) 227 | .kmsProviders(kmsProviders) 228 | .build(); 229 | 230 | return ClientEncryptions.create(clientEncryptionSettings); 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /nodejs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | es6: true, 5 | }, 6 | parserOptions: { 7 | sourceType: "module", 8 | allowImportExportEverywhere: true, 9 | ecmaVersion: 8, 10 | }, 11 | root: true, 12 | extends: ["eslint:recommended", "plugin:prettier/recommended"], 13 | }; 14 | -------------------------------------------------------------------------------- /nodejs/README.md: -------------------------------------------------------------------------------- 1 | # Node CSFLE Example 2 | 3 | ## Steps 4 | 5 | 1. Clone this repository and navigate to the **nodejs** directory. 6 | 7 | ```sh 8 | git clone https://github.com/mongodb-university/csfle-guides.git 9 | cd nodejs 10 | ``` 11 | 12 | Work from the **nodejs** directory for the remainder of these instructions. 13 | 14 | 2. Start a locally running `mongod` instance (Enterprise version >= 4.2) running on port 27017 15 | 16 | 3. Install the dependencies in `package.json`. Node version 10 or 12 is required. 17 | 18 | ```js 19 | npm install 20 | ``` 21 | 22 | 4. Make sure you have the `master-key.txt` file in the root of your execution 23 | environment. This is a 96-byte cryptographically-secure generated master 24 | encryption key required to run this example project. To generate your own 25 | master key or use a KMS, refer to the [CSFLE Use Case Guide](https://www.mongodb.com/drivers/security/client-side-field-level-encryption-guide/). 26 | 27 | 5. Edit the line 28 | 29 | ```js 30 | const kmsClient = kms.localCsfleHelper(); 31 | ``` 32 | 33 | to your preferred KMS provider (local, aws, azure, or gcp). The available KMS 34 | providers and the values necessary to use them are listed in `kms.js`. In this project, 35 | environmental variables are used and manged with a [`.env` file](https://www.npmjs.com/package/dotenv). 36 | 37 | 6. Run the `make-data-key.js` script to make a data key. If there is an 38 | existing data key in the **encryption.\_\_keyVault** collection this script 39 | will not create a duplicate data key. 40 | 41 | ```js 42 | node make-data-key.js 43 | ``` 44 | 45 | This outputs a base64 encoded string of the UUID of your newly created data key. Paste 46 | this into `clients.js` where you see this line 47 | 48 | ```js 49 | let dataKey = null; 50 | ``` 51 | 52 | 7. Edit the line 53 | 54 | ```js 55 | const kmsClient = kms.localCsfleHelper(); 56 | ``` 57 | 58 | to your prefered KMS provider, the same used in `make-data-key.js`.` 59 | 60 | 8. Run the `clients.js` script to insert a document with the CSFLE-enabled client 61 | and then read that document with it as well as a regular client. You 62 | will see that the CSFLE-enabled client prints the document out in plaintext, 63 | and the regular client prints the document out with encrypted fields in 64 | binary format. This is safe to run multiple times as the insert operation 65 | used is an update with `upsert` specified. 66 | 67 | ```js 68 | node clients.js 69 | ``` 70 | 71 | 9. Suggestion: Try inserting a document with the regular client. What happens? 72 | -------------------------------------------------------------------------------- /nodejs/clients.js: -------------------------------------------------------------------------------- 1 | const kms = require("./kms"); 2 | require("dotenv").config(); 3 | 4 | const kmsClient = kms.localCsfleHelper(); 5 | 6 | async function main(regularClient, csfleClient) { 7 | try { 8 | let dataKey = "RQo8Da4DTRSGrf8FN30kGw=="; // change this to the base64 encoded data key generated from make-data-key.js 9 | if (dataKey === null) { 10 | Error.stackTraceLimit = 1ed 11 | let err = new Error( 12 | `dataKey is required. 13 | Run make-data-key.js and ensure you copy and paste the output into client.js 14 | ` 15 | ); 16 | throw err; 17 | } 18 | 19 | regularClient = await kmsClient.getRegularClient(); 20 | let schemaMap = kmsClient.createJsonSchemaMap(dataKey); 21 | csfleClient = await kmsClient.getCsfleEnabledClient(schemaMap); 22 | 23 | let exampleDocument = { 24 | name: "Jon Doe", 25 | ssn: 241014209, 26 | bloodType: "AB+", 27 | medicalRecords: [ 28 | { 29 | weight: 180, 30 | bloodPressure: "120/80", 31 | }, 32 | ], 33 | insurance: { 34 | provider: "MaestCare", 35 | policyNumber: 123142, 36 | }, 37 | }; 38 | 39 | const regularClientPatientsColl = regularClient 40 | .db("medicalRecords") 41 | .collection("patients"); 42 | const csfleClientPatientsColl = csfleClient 43 | .db("medicalRecords") 44 | .collection("patients"); 45 | 46 | // Performs the insert operation with the csfle-enabled client 47 | // We're using an update with an upsert so that subsequent runs of this script 48 | // don't insert new documents 49 | await csfleClientPatientsColl.updateOne( 50 | { ssn: exampleDocument["ssn"] }, 51 | { $set: exampleDocument }, 52 | { upsert: true } 53 | ); 54 | 55 | // Performs a read using the encrypted client, querying on an encrypted field 56 | const csfleFindResult = await csfleClientPatientsColl.findOne({ 57 | ssn: exampleDocument["ssn"], 58 | }); 59 | console.log( 60 | "Document retrieved with csfle enabled client:\n", 61 | csfleFindResult 62 | ); 63 | 64 | // Performs a read using the regular client. We must query on a field that is 65 | // not encrypted. 66 | // Try - query on the ssn field. What is returned? 67 | const regularFindResult = await regularClientPatientsColl.findOne({ 68 | name: "Jon Doe", 69 | }); 70 | console.log("Document retrieved with regular client:\n", regularFindResult); 71 | } finally { 72 | if (regularClient) await regularClient.close(); 73 | if (csfleClient) await csfleClient.close(); 74 | } 75 | } 76 | 77 | let regularClient = null; 78 | let csfleClient = null; 79 | main(regularClient, csfleClient).catch(console.dir); 80 | -------------------------------------------------------------------------------- /nodejs/helpers.js: -------------------------------------------------------------------------------- 1 | const mongodb = require("mongodb"); 2 | const { ClientEncryption } = require("mongodb-client-encryption"); 3 | const { MongoClient, Binary } = mongodb; 4 | 5 | module.exports = { 6 | CsfleHelper: class { 7 | constructor({ 8 | provider = null, 9 | kmsProviders = null, 10 | masterKey = null, 11 | keyAltNames = "demo-data-key", 12 | keyDB = "encryption", 13 | keyColl = "__keyVault", 14 | schema = null, 15 | connectionString = "mongodb://localhost:27017", 16 | mongocryptdBypassSpawn = false, 17 | mongocryptdSpawnPath = "mongocryptd", 18 | } = {}) { 19 | if (kmsProviders === null) { 20 | throw new Error("kmsProviders is required"); 21 | } 22 | if (provider === null) { 23 | throw new Error("provider is required"); 24 | } 25 | if (provider !== "local" && masterKey === null) { 26 | throw new Error("masterKey is required"); 27 | } 28 | this.kmsProviders = kmsProviders; 29 | this.masterKey = masterKey; 30 | this.provider = provider; 31 | this.keyAltNames = keyAltNames; 32 | this.keyDB = keyDB; 33 | this.keyColl = keyColl; 34 | this.keyVaultNamespace = `${keyDB}.${keyColl}`; 35 | this.schema = schema; 36 | this.connectionString = connectionString; 37 | this.mongocryptdBypassSpawn = mongocryptdBypassSpawn; 38 | this.mongocryptdSpawnPath = mongocryptdSpawnPath; 39 | this.regularClient = null; 40 | this.csfleClient = null; 41 | } 42 | 43 | /** 44 | * Creates a unique, partial index in the key vault collection 45 | * on the ``keyAltNames`` field. 46 | * 47 | * @param {MongoClient} client 48 | */ 49 | async ensureUniqueIndexOnKeyVault(client) { 50 | try { 51 | await client 52 | .db(this.keyDB) 53 | .collection(this.keyColl) 54 | .createIndex("keyAltNames", { 55 | unique: true, 56 | partialFilterExpression: { 57 | keyAltNames: { 58 | $exists: true, 59 | }, 60 | }, 61 | }); 62 | } catch (e) { 63 | throw new Error(e); 64 | } 65 | } 66 | 67 | /** 68 | * In the guide, https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-guide/, 69 | * we create the data key and then show that it is created by 70 | * retreiving it using a findOne query. Here, in implementation, we only 71 | * create the key if it doesn't already exist, ensuring we only have one 72 | * local data key. 73 | * 74 | * @param {MongoClient} client 75 | */ 76 | async findOrCreateDataKey(client) { 77 | const encryption = new ClientEncryption(client, { 78 | keyVaultNamespace: this.keyVaultNamespace, 79 | kmsProviders: this.kmsProviders, 80 | }); 81 | 82 | await this.ensureUniqueIndexOnKeyVault(client); 83 | 84 | let dataKey = await client 85 | .db(this.keyDB) 86 | .collection(this.keyColl) 87 | .findOne({ keyAltNames: { $in: [this.keyAltNames] } }); 88 | 89 | if (dataKey === null) { 90 | dataKey = await encryption.createDataKey(this.provider, { 91 | masterKey: this.masterKey, 92 | }); 93 | return dataKey.toString("base64"); 94 | } 95 | return dataKey["_id"].toString("base64"); 96 | } 97 | 98 | async getRegularClient() { 99 | const client = new MongoClient(this.connectionString, { 100 | useNewUrlParser: true, 101 | useUnifiedTopology: true, 102 | }); 103 | return await client.connect(); 104 | } 105 | 106 | async getCsfleEnabledClient(schemaMap = null) { 107 | if (schemaMap === null) { 108 | throw new Error( 109 | "schemaMap is a required argument. Build it using the CsfleHelper.createJsonSchemaMap method" 110 | ); 111 | } 112 | const client = new MongoClient(this.connectionString, { 113 | useNewUrlParser: true, 114 | useUnifiedTopology: true, 115 | monitorCommands: true, 116 | autoEncryption: { 117 | keyVaultNamespace: this.keyVaultNamespace, 118 | kmsProviders: this.kmsProviders, 119 | schemaMap, 120 | }, 121 | }); 122 | return await client.connect(); 123 | } 124 | 125 | createJsonSchemaMap(dataKey) { 126 | return { 127 | "medicalRecords.patients": { 128 | bsonType: "object", 129 | encryptMetadata: { 130 | keyId: [new Binary(Buffer.from(dataKey, "base64"), 4)], 131 | }, 132 | properties: { 133 | insurance: { 134 | bsonType: "object", 135 | properties: { 136 | policyNumber: { 137 | encrypt: { 138 | bsonType: "int", 139 | algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", 140 | }, 141 | }, 142 | }, 143 | }, 144 | medicalRecords: { 145 | encrypt: { 146 | bsonType: "array", 147 | algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Random", 148 | }, 149 | }, 150 | bloodType: { 151 | encrypt: { 152 | bsonType: "string", 153 | algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Random", 154 | }, 155 | }, 156 | ssn: { 157 | encrypt: { 158 | bsonType: "int", 159 | algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", 160 | }, 161 | }, 162 | }, 163 | }, 164 | }; 165 | } 166 | }, 167 | }; 168 | -------------------------------------------------------------------------------- /nodejs/kms.js: -------------------------------------------------------------------------------- 1 | const { CsfleHelper } = require("./helpers"); 2 | const fs = require("fs"); 3 | 4 | const envToKey = { 5 | // aws credentials 6 | FLE_AWS_ACCESS_KEY: "accessKeyId", 7 | FLE_AWS_SECRET_ACCESS_KEY: "secretAccessKey", 8 | // aws masterKey information 9 | FLE_AWS_KEY_ARN: "key", 10 | FLE_AWS_KEY_REGION: "region", 11 | FLE_AWS_KEY_ENDPOINT: "endpoint", // optional, AWS KMS URL 12 | 13 | // gcp credentials 14 | FLE_GCP_EMAIL: "email", 15 | FLE_GCP_PRIVATE_KEY: "privateKey", 16 | FLE_GCP_CRED_ENDPOINT: "endpoint", // optional, defaults to "oauth2.googleapis.com" 17 | // gcp masterKey information 18 | FLE_GCP_PROJ_ID: "projectId", 19 | FLE_GCP_LOCATION: "location", 20 | FLE_GCP_KEY_RING: "keyRing", 21 | FLE_GCP_KEY_NAME: "keyName", 22 | FLE_GCP_KEY_ENDPOINT: "endpoint", // optional 23 | FLE_GCP_KEY_VERSION: "keyVersion", // optional, defaults to "googleapis.com/auth/cloudkms" 24 | 25 | // azure credentials 26 | FLE_AZURE_TENANT_ID: "tenantId", 27 | FLE_AZURE_CLIENT_ID: "clientId", 28 | FLE_AZURE_CLIENT_SECRET: "clientSecret", 29 | FLE_AZURE_IDENTITY_PLATFORM_ENDPOINT: "identityPlatformEndpoint", // optional, defaults to "login.microsoftonline.com" 30 | // azure masterKey information 31 | FLE_AZURE_KEY_NAME: "keyName", 32 | FLE_AZURE_KEYVAULT_ENDPOINT: "keyVaultEndpoint", 33 | FLE_AZURE_KEY_VERSION: "keyVersion", // optional 34 | }; 35 | 36 | function getCheckedEnv(...envVars) { 37 | if (envVars.some((elem) => !process.env[elem])) { 38 | throw new Error( 39 | `Found no environmental variables set for the following values: ${envVars 40 | .filter((elem) => !process.env[elem]) 41 | .join(", ")}` 42 | ); 43 | } else { 44 | return envVars.reduce((acc, curr) => { 45 | acc[envToKey[curr]] = process.env[curr]; 46 | return acc; 47 | }, {}); 48 | } 49 | } 50 | 51 | function readMasterKey(path = "./master-key.txt") { 52 | return fs.readFileSync(path); 53 | } 54 | 55 | module.exports = { 56 | localCsfleHelper: (path = "./master-key.txt") => { 57 | return new CsfleHelper({ 58 | provider: "local", 59 | kmsProviders: { 60 | local: { 61 | key: readMasterKey(path), 62 | }, 63 | }, 64 | }); 65 | }, 66 | 67 | awsCsfleHelper: () => { 68 | const { accessKeyId, secretAccessKey, key, region } = getCheckedEnv( 69 | "FLE_AWS_ACCESS_KEY", 70 | "FLE_AWS_SECRET_ACCESS_KEY", 71 | "FLE_AWS_KEY_ARN", 72 | "FLE_AWS_KEY_REGION" 73 | ); 74 | return new CsfleHelper({ 75 | provider: "aws", 76 | kmsProviders: { 77 | aws: { 78 | accessKeyId, 79 | secretAccessKey, 80 | }, 81 | }, 82 | masterKey: { 83 | key, 84 | region, 85 | }, 86 | }); 87 | }, 88 | 89 | gcpCsfleHelper: () => { 90 | const { 91 | email, 92 | privateKey, 93 | projectId, 94 | location, 95 | keyRing, 96 | keyName, 97 | } = getCheckedEnv( 98 | "FLE_GCP_EMAIL", 99 | "FLE_GCP_PRIVATE_KEY", 100 | "FLE_GCP_PROJ_ID", 101 | "FLE_GCP_LOCATION", 102 | "FLE_GCP_KEY_RING", 103 | "FLE_GCP_KEY_NAME" 104 | ); 105 | return new CsfleHelper({ 106 | provider: "gcp", 107 | kmsProviders: { 108 | gcp: { 109 | email, 110 | privateKey, 111 | }, 112 | }, 113 | masterKey: { 114 | projectId, 115 | location, 116 | keyRing, 117 | keyName, 118 | }, 119 | }); 120 | }, 121 | 122 | azureCsfleHelper: () => { 123 | const { 124 | tenantId, 125 | clientId, 126 | clientSecret, 127 | keyVaultEndpoint, 128 | keyName, 129 | } = getCheckedEnv( 130 | "FLE_AZURE_TENANT_ID", 131 | "FLE_AZURE_CLIENT_ID", 132 | "FLE_AZURE_CLIENT_SECRET", 133 | "FLE_AZURE_KEYVAULT_ENDPOINT", 134 | "FLE_AZURE_KEY_NAME" 135 | ); 136 | return new CsfleHelper({ 137 | provider: "azure", 138 | kmsProviders: { 139 | azure: { 140 | tenantId, 141 | clientId, 142 | clientSecret, 143 | }, 144 | }, 145 | masterKey: { 146 | keyVaultEndpoint, 147 | keyName, 148 | }, 149 | }); 150 | }, 151 | }; 152 | -------------------------------------------------------------------------------- /nodejs/make-data-key.js: -------------------------------------------------------------------------------- 1 | const kms = require("./kms"); 2 | require("dotenv").config(); 3 | 4 | async function main(unencryptedClient) { 5 | try { 6 | const kmsClient = kms.localCsfleHelper(); 7 | 8 | unencryptedClient = await kmsClient.getRegularClient(); 9 | 10 | const dataKey = await kmsClient.findOrCreateDataKey(unencryptedClient); 11 | console.log( 12 | `Base64 data key. Copy and paste this into clients.js: 13 | 14 | ${dataKey} 15 | ` 16 | ); 17 | } finally { 18 | if (unencryptedClient) await unencryptedClient.close(); 19 | } 20 | } 21 | let unencryptedClient = null; 22 | main(unencryptedClient).catch(console.dir); 23 | -------------------------------------------------------------------------------- /nodejs/master-key.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb-university/csfle-guides/fdc3cdfc4890b2bc3e8fd4f40a54a013b7b93a53/nodejs/master-key.txt -------------------------------------------------------------------------------- /nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csfle", 3 | "version": "1.0.0", 4 | "description": "A sample implementation of field level encryption in MongoDB, meant to accompany the CSFLE guide at https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-guide/", 5 | "main": "clients.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/mongodb-university/csfle-guides.git" 12 | }, 13 | "author": "Developer Education Team", 14 | "license": "Apache-2.0", 15 | "bugs": { 16 | "url": "https://github.com/mongodb-university/csfle-guides/issues" 17 | }, 18 | "homepage": "https://github.com/mongodb-university/csfle-guides#readme", 19 | "dependencies": { 20 | "dotenv": "^8.2.0", 21 | "mongodb": "^4.1.1", 22 | "mongodb-client-encryption": "1.2.6" 23 | }, 24 | "prettier": { 25 | "semi": true 26 | }, 27 | "devDependencies": { 28 | "eslint": "^7.15.0", 29 | "prettier": "^2.2.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # Python CSFLE Example 2 | 3 | ## Steps 4 | 5 | 1. Clone this repository and navigate to the **python** directory. 6 | 7 | ```sh 8 | git clone https://github.com/mongodb-university/csfle-guides.git 9 | cd python 10 | ``` 11 | 12 | Work from the **python** directory for the remainder of these instructions. 13 | 14 | 2. Start a locally running `mongod` instance (Enterprise version >= 4.2) running on port 27017 15 | 16 | 3. We recommend that you create a local environment to encapsulate the 17 | dependencies. We use [**pyenv**](https://realpython.com/intro-to-pyenv/) 18 | 19 | ``` 20 | pyenv virtualenv 3.7.3 csfle 21 | pyenv local csfle 22 | pip install -r requirements.txt 23 | ``` 24 | 25 | If you need to use AWS KMS, install the `pymongocrypt` library using the 26 | following command: 27 | 28 | ``` 29 | pip install pymongocrypt==1.0.1 30 | ``` 31 | 32 | If you need to use Azure or GCP KMS, you need to build and install the 33 | `pymongocrypt` library using our instructions on 34 | [how to build libmongocrypt from source](https://github.com/mongodb/libmongocrypt/tree/master/bindings/python#installing-from-source). 35 | 36 | 4. Make sure you have the `master-key.txt` file in the root of your execution 37 | environment. This is a 96-byte cryptographically-secure generated master 38 | encryption key required to run this example project. To generate your own 39 | master key or use a KMS, refer to the [CSFLE Use Case Guide](https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-guide/). 40 | 41 | The settings for each supported KMS are included in `app.py`. If you are 42 | using a cloud KMS provider, uncomment and assign your KMS provider 43 | settings. See 44 | [Use a KMS to Store the Master Key Guide](https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-local-key-to-kms) 45 | for more information on how to set up a master key and data encryption 46 | key with on of the supported KMS providers. 47 | 48 | 49 | 5. Run the `make_data_key.py` script to make a data key. If there is an 50 | existing data key in the **encryption.__keyVault** collection this script 51 | will not create a duplicate data key. 52 | 53 | ```python 54 | python make_data_key.py 55 | ``` 56 | 57 | This outputs a base64 encoded string of the UUID of your newly created data key. Paste 58 | this into `app.py` where you see this line 59 | 60 | ```python 61 | # Insert your key generated by make_data_key.py here. 62 | # Or comment this out if you already have a data key for your provider stored. 63 | data_key = CsfleHelper.key_from_base64("") 64 | ``` 65 | 66 | 6. Run the `app.py` script to insert a document with the CSFLE-enabled client 67 | and then read that document with it as well as a regular client. The 68 | CSFLE-enabled client prints the document out in plaintext, and the regular 69 | client prints the document out with encrypted fields in binary format. 70 | This is safe to run multiple times; the insert operation is called with 71 | an upsert option specified. 72 | 73 | ```python 74 | python app.py 75 | ``` 76 | 77 | 7. Suggestion: Try inserting a document with the regular client. What happens? 78 | -------------------------------------------------------------------------------- /python/app.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-present MongoDB, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | from helpers import read_master_key, CsfleHelper 18 | 19 | """ 20 | This app demonstrates CSFLE in action. If you do not already have a data 21 | encryption key and are using the local master key provider, make sure to run 22 | make_data_key.py first. 23 | """ 24 | 25 | 26 | def main(): 27 | 28 | # For local master key 29 | master_key = read_master_key() 30 | kms_provider_name = "local" 31 | kms_provider = { 32 | "local": { 33 | "key": master_key, 34 | }, 35 | } 36 | 37 | """ 38 | For AWS KMS, uncomment this block. See https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-local-key-to-kms 39 | for more information on storing a master key on a KMS. 40 | 41 | kms_provider_name = "aws" 42 | kms_providers = { 43 | "aws": { 44 | "accessKeyId": "", 45 | "secretAccessKey": "" 46 | } 47 | } 48 | 49 | master_key = { 50 | "aws": { 51 | "region": "", 52 | "key": "", 53 | "endpoint": "" # optional 54 | } 55 | 56 | """ 57 | 58 | """ 59 | For Azure KMS, uncomment this block. See https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-local-key-to-kms 60 | for more information on storing a master key on a KMS. 61 | 62 | kms_provider_name = "azure" 63 | kms_provider = { 64 | "azure": { 65 | "tenantId": "", 66 | "clientId": "", 67 | "clientSecret": "", 68 | "identityPlatformEndpoint": "", # optional 69 | } 70 | } 71 | 72 | master_key = { 73 | "keyName": "", 74 | "keyVaultEndpoint": "", 75 | "keyVersion": "", 76 | } 77 | """ 78 | 79 | """ 80 | For GCP KMS, uncomment this block. See https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-local-key-to-kms 81 | for more information on storing a master key on a KMS. 82 | 83 | kms_provider_name = "gcp" 84 | kms_provider = { 85 | "gcp": { 86 | "projectId": "", 87 | "location": "", 88 | "keyRing": "", 89 | "keyName": "", 90 | "keyVersion": "", # optional 91 | } 92 | } 93 | 94 | master_key = { 95 | "email": "", 96 | "privateKey": "", 97 | "endpoint": "", # optional 98 | } 99 | """ 100 | 101 | keyDb = "encryption" 102 | keyColl = "__keyVault" 103 | 104 | dataDb = "records" 105 | dataColl = "patients" 106 | 107 | example_document = { 108 | "name": "Jon Doe", 109 | "ssn": 241014209, 110 | "bloodType": "AB+", 111 | "medicalRecords": [ 112 | { 113 | "weight": 180, 114 | "bloodPressure": "120/80" 115 | } 116 | ], 117 | "insurance": { 118 | "provider": "MaestCare", 119 | "policyNumber": 123142 120 | }, 121 | } 122 | 123 | csfle_helper = CsfleHelper(kms_provider_name=kms_provider_name, 124 | kms_provider=kms_provider, master_key=master_key, key_db=keyDb, key_coll=keyColl) 125 | 126 | # Insert your key generated by make_data_key.py here. 127 | # Or comment this out if you already have a data key for your provider stored. 128 | data_key = CsfleHelper.key_from_base64( 129 | "") 130 | 131 | # if you already have a data key or are using a remote KMS, uncomment the line below 132 | #data_key = csfle_helper.find_or_create_data_key() 133 | 134 | # set a JSON schema for automatic encryption 135 | schema = CsfleHelper.create_json_schema( 136 | data_key=data_key, dbName=dataDb, collName=dataColl) 137 | 138 | encrypted_client = csfle_helper.get_csfle_enabled_client(schema) 139 | 140 | # performing the insert operation with the CSFLE-enabled client 141 | # we're using an update with upsert so that subsequent runs of this script don't 142 | # add more documents 143 | encrypted_client.records.patients.update_one( 144 | {"ssn": example_document["ssn"]}, 145 | {"$set": example_document}, upsert=True) 146 | 147 | # perform a read using the csfle enabled client. We expect all fields to 148 | # be readable. 149 | # querying on an encrypted field using strict equality 150 | csfle_find_result = encrypted_client.records.patients.find_one( 151 | {"ssn": example_document["ssn"]}) 152 | print( 153 | f"Document retrieved with csfle enabled client:\n{csfle_find_result}\n") 154 | encrypted_client.close() 155 | 156 | # perform a read using the regular client. We expect some fields to be 157 | # encrypted. 158 | regular_client = csfle_helper.get_regular_client() 159 | regular_find_result = regular_client.records.patients.find_one({ 160 | "name": "Jon Doe"}) 161 | print(f"Document found regular_find_result:\n{regular_find_result}") 162 | regular_client.close() 163 | 164 | 165 | if __name__ == "__main__": 166 | main() 167 | -------------------------------------------------------------------------------- /python/helpers.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-present MongoDB, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | """Helpers for CSFLE implementation.""" 18 | 19 | import base64 20 | 21 | from pymongo import MongoClient 22 | import pymongocrypt 23 | from pymongo.encryption_options import AutoEncryptionOpts 24 | from pymongo.encryption import ClientEncryption 25 | from bson.codec_options import CodecOptions 26 | from bson.binary import Binary, STANDARD, UUID_SUBTYPE 27 | from uuid import UUID 28 | 29 | 30 | def read_master_key(path="./master-key.txt"): 31 | with open(path, "rb") as f: 32 | return f.read(96) 33 | 34 | 35 | class CsfleHelper: 36 | """This is a helper class that aids in csfle implementation.""" 37 | 38 | def __init__(self, 39 | kms_provider=None, 40 | kms_provider_name="local", 41 | key_alt_name="demo-data-key", 42 | key_db="encryption", 43 | key_coll="__keyVault", 44 | master_key=None, 45 | schema=None, 46 | connection_string="mongodb://localhost:27017", 47 | # setting this to True requires manually running mongocryptd 48 | mongocryptd_bypass_spawn=False, 49 | mongocryptd_spawn_path="mongocryptd"): 50 | """ 51 | If mongocryptd is not installed to in your search path, ensure 52 | you override mongocryptd_spawn_path 53 | """ 54 | super().__init__() 55 | if kms_provider is None: 56 | raise ValueError("kms_provider is required") 57 | self.kms_provider = kms_provider 58 | self.kms_provider_name = kms_provider_name 59 | self.key_alt_name = key_alt_name 60 | self.key_db = key_db 61 | self.key_coll = key_coll 62 | self.master_key = master_key 63 | self.key_vault_namespace = f"{self.key_db}.{self.key_coll}" 64 | self.schema = schema 65 | self.connection_string = connection_string 66 | self.mongocryptd_bypass_spawn = mongocryptd_bypass_spawn 67 | self.mongocryptd_spawn_path = mongocryptd_spawn_path 68 | 69 | def key_from_base64(base64_key): 70 | return Binary(base64.b64decode(base64_key), UUID_SUBTYPE) 71 | 72 | def ensure_unique_index_on_key_vault(self, key_vault): 73 | # clients are required to create a unique partial index on keyAltNames 74 | key_vault.create_index("keyAltNames", 75 | unique=True, 76 | partialFilterExpression={ 77 | "keyAltNames": { 78 | "$exists": True 79 | } 80 | }) 81 | 82 | def find_or_create_data_key(self): 83 | """ 84 | In the guide, https://www.mongodb.com/docs/drivers/security/client-side-field-level-encryption-guide/, 85 | we create the data key and then show that it is created by 86 | using a find_one query. Here, in implementation, we only create the key if 87 | it doesn't already exist, ensuring we only have one local data key. 88 | 89 | We also use the key_alt_names field and provide a key alt name to aid in 90 | finding the key in the clients.py script. 91 | """ 92 | 93 | key_vault_client = MongoClient(self.connection_string) 94 | 95 | key_vault = key_vault_client[self.key_db][self.key_coll] 96 | 97 | self.ensure_unique_index_on_key_vault(key_vault) 98 | 99 | data_key = key_vault.find_one({"keyAltNames": self.key_alt_name}) 100 | 101 | # create a key 102 | if data_key is None: 103 | with ClientEncryption(self.kms_provider, 104 | self.key_vault_namespace, 105 | key_vault_client, 106 | CodecOptions(uuid_representation=STANDARD) 107 | ) as client_encryption: 108 | 109 | # create data key using KMS master key 110 | return client_encryption.create_data_key( 111 | self.kms_provider_name, 112 | key_alt_names=[self.key_alt_name], 113 | master_key=self.master_key) 114 | 115 | return data_key['_id'].bytes 116 | 117 | def get_regular_client(self): 118 | return MongoClient(self.connection_string) 119 | 120 | def get_csfle_enabled_client(self, schema): 121 | return MongoClient( 122 | self.connection_string, 123 | auto_encryption_opts=AutoEncryptionOpts( 124 | self.kms_provider, 125 | self.key_vault_namespace, 126 | mongocryptd_bypass_spawn=self.mongocryptd_bypass_spawn, 127 | mongocryptd_spawn_path=self.mongocryptd_spawn_path, 128 | schema_map=schema) 129 | ) 130 | 131 | def create_json_schema(data_key, dbName, collName): 132 | collNamespace = f"{dbName}.{collName}" 133 | return { 134 | collNamespace: { 135 | "bsonType": "object", 136 | "encryptMetadata": { 137 | "keyId": [data_key] 138 | }, 139 | "properties": { 140 | "insurance": { 141 | "bsonType": "object", 142 | "properties": { 143 | "policyNumber": { 144 | "encrypt": { 145 | "bsonType": "int", 146 | "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" 147 | } 148 | } 149 | } 150 | }, 151 | "medicalRecords": { 152 | "encrypt": { 153 | "bsonType": "array", 154 | "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" 155 | } 156 | }, 157 | "bloodType": { 158 | "encrypt": { 159 | "bsonType": "string", 160 | "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" 161 | } 162 | }, 163 | "ssn": { 164 | "encrypt": { 165 | "bsonType": "int", 166 | "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" 167 | } 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /python/make_data_key.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019-present MongoDB, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | """This file finds and prints or makes and prints a data key used for 18 | encrpytion.""" 19 | 20 | import base64 21 | from uuid import UUID 22 | from helpers import read_master_key, CsfleHelper 23 | 24 | 25 | def main(): 26 | 27 | local_master_key = read_master_key() 28 | 29 | kms_provider = { 30 | "local": { 31 | "key": local_master_key, 32 | }, 33 | } 34 | 35 | csfle_helper = CsfleHelper(kms_provider=kms_provider) 36 | binary_data_key = csfle_helper.find_or_create_data_key() 37 | data_key = base64.b64encode(binary_data_key).decode("utf-8") 38 | 39 | print("Base64 data key. Copy and paste this into app.py\t", data_key) 40 | 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /python/master-key.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb-university/csfle-guides/fdc3cdfc4890b2bc3e8fd4f40a54a013b7b93a53/python/master-key.txt -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | autopep8==1.4.4 2 | cffi==1.13.2 3 | cryptography==3.3.2 4 | dnspython==1.16.0 5 | entrypoints==0.3 6 | flake8==3.7.9 7 | greenlet==0.4.15 8 | mccabe==0.6.1 9 | msgpack==0.6.2 10 | pycodestyle==2.5.0 11 | pycparser==2.19 12 | pyflakes==2.1.1 13 | pymongo==3.11.0 14 | pynvim==0.3.2 15 | six==1.13.0 16 | --------------------------------------------------------------------------------