Skip to content

Commit

Permalink
MONGOSH-988 java-shell package should use proper crypto.randomBytes()…
Browse files Browse the repository at this point in the history
… polyfill
  • Loading branch information
kornilova203 committed Sep 15, 2023
1 parent 044fd6b commit 231a95e
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 41 deletions.
42 changes: 20 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 7 additions & 10 deletions packages/java-shell/src/main/js/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,18 @@ process.cwd = process.cwd || (() => '/');
globalThis.btoa = globalThis.btoa || (data => Buffer.from(data, 'latin1').toString('base64'));
globalThis.atob = globalThis.atob || (data => Buffer.from(data, 'base64').toString('latin1'));

// The BSON package tries to use crypto.randomBytes(), but that throws
// in the current browserify replacement for that package.
// We provide a low-quality polyfill for now.
// https://jira.mongodb.org/browse/MONGOSH-988
// crypto.randomBytes() is required by BSON package
// but that throws error in the current browserify replacement for that package.
// Here we create crypto object that will be linked to BSON package
// we provide proper implementation of randomBytes in Kotlin in MongoShellEvaluator
const crypto = require('crypto');
try {
crypto.randomBytes(1);
} catch (err) {
crypto.randomBytes = function(size) {
const uint8Array = new Uint8Array(size);
for (var i = 0; i < uint8Array.length; i++) {
uint8Array[i] = Math.random() * 256;
}
return size;
throw new Error('randomBytes is not implemented');
};
};
}

require('../../../../service-provider-core'); // Ensure TextEncoder polyfill is loaded early enough
const ShellApi = require('../../../../shell-api/');
Expand All @@ -34,4 +30,5 @@ _global = {
ShellEvaluator: ShellEvaluator,
toShellResult: ShellApi.toShellResult,
getShellApiType: ShellApi.getShellApiType,
crypto: crypto
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.mongodb.mongosh.service.Either
import com.mongodb.mongosh.service.Left
import com.mongodb.mongosh.service.Right
import org.bson.*
import org.bson.internal.UuidHelper
import org.bson.json.JsonReader
import org.bson.types.*
import org.graalvm.polyglot.Value
Expand Down Expand Up @@ -93,6 +94,14 @@ internal class MongoShellConverter(private val context: MongoShellContext, priva
return array
}

fun toBuffer(array: ByteArray): Value {
val jsArray = context.eval("new Buffer(${array.size})")
array.forEachIndexed { index, v ->
jsArray.setArrayElement(index.toLong(), v)
}
return jsArray
}

fun <T> toJsPromise(promise: Either<T>): Value {
return when (promise) {
is Right -> context.eval("(v) => new Promise(((resolve) => resolve(v)))", "resolved_promise_script").execute(toJs(promise.value))
Expand Down Expand Up @@ -189,7 +198,15 @@ internal class MongoShellConverter(private val context: MongoShellContext, priva
v.instanceOf(context, bsonTypes.numberLong) -> LongResult(JsonReader(v.invokeMember("toExtendedJSON").toString()).readInt64())
v.instanceOf(context, bsonTypes.binData) -> {
val binary = JsonReader(v.invokeMember("toExtendedJSON").toString()).readBinaryData()
BinaryResult(Binary(binary.type, binary.data))
val uuidRepresentation = when (binary.type) {
BsonBinarySubType.UUID_STANDARD.value -> UuidRepresentation.STANDARD
BsonBinarySubType.UUID_LEGACY.value -> UuidRepresentation.JAVA_LEGACY
else -> null
}
if (uuidRepresentation != null) {
UUIDResult(UuidHelper.decodeBinaryToUuid(binary.data, binary.type, uuidRepresentation))
}
else BinaryResult(Binary(binary.type, binary.data))
}
v.instanceOf(context, bsonTypes.hexData) -> {
val binary = JsonReader(v.invokeMember("toExtendedJSON").toString()).readBinaryData()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.graalvm.polyglot.Source
import org.graalvm.polyglot.Value
import org.graalvm.polyglot.proxy.ProxyExecutable
import org.intellij.lang.annotations.Language
import java.security.SecureRandom
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
Expand Down Expand Up @@ -37,7 +38,7 @@ internal class MongoShellEvaluator(client: MongoClient?, private val context: Mo
toShellResultFn = global.getMember("toShellResult")
getShellApiTypeFn = global.getMember("getShellApiType")
shellInstanceState.invokeMember("setCtx", context.bindings)
initContext(context.bindings)
initContext(context.bindings, global)
}

private fun resultHandler() = context.jsFun { args ->
Expand All @@ -51,13 +52,24 @@ internal class MongoShellEvaluator(client: MongoClient?, private val context: Mo
}
}

private fun initContext(bindings: Value) {
private fun initContext(bindings: Value, global: Value) {
val date = context.eval("(dateHelper) => function inner() { return dateHelper(new.target !== undefined, ...arguments) }", "dateHelper_script")
.execute(ProxyExecutable { args -> dateHelper(args[0].asBoolean(), args.drop(1)) })
.execute(ProxyExecutable { args -> dateHelper(args[0].asBoolean(), args.drop(1)) })
date["now"] = ProxyExecutable { System.currentTimeMillis() }
bindings["Date"] = date
bindings["ISODate"] = context.jsFun { args -> dateHelper(true, args.toList()) }
bindings["UUID"] = context.jsFun { args -> if (args.isEmpty()) UUID.randomUUID() else UUID.fromString(args[0].asString()) }
val secureRandom = SecureRandom()
global["crypto"]!!["randomBytes"] = context.jsFun { args ->
// randomBytes method is used to generate UUID bson object
// here we use SecureRandom to generate cryptographically strong random numbers
// java.util.UUID also uses SecureRandom
if (args.size != 1) {
throw IllegalArgumentException("Expected one argument. Got ${args.size} ${args.contentToString()}")
}
val randomBytes = ByteArray(args[0].asInt())
secureRandom.nextBytes(randomBytes)
return@jsFun converter.toBuffer(randomBytes) // Buffer must be used because .toString('hex') will be invoked on the object
}
}

private fun shellResult(printable: Value, type: String): Value {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ true
[ { "_id": <ObjectID>, "a": 1, "objectId": <ObjectID>, "maxKey": {"$maxKey": 1}, "minKey": {"$minKey": 1}, "binData": {"$binary": {"base64": "MTIzNA==", "subType": "10"}}, "date": {"$date": {"$numberLong": "1355875200000"}}, "isoDate": {"$date": {"$numberLong": "1355875200000"}}, "numberInt": 24, "timestamp": Timestamp{value=429496729600, seconds=100, inc=0}, "undefined": null, "null": null, "uuid": <UUID> } ]
{ "acknowledged": true, "insertedId": null }
{ "acknowledged": true, "insertedId": {"$date": {"$numberLong": "1355875200000"}} }
{ "acknowledged": true, "insertedId": {"$binary": {"base64": "ASNFZ4mrze8BI0VniavN7w==", "subType": "04"}} }
{ "acknowledged": true, "insertedId": <UUID> }
{ "acknowledged": true, "insertedId": {"$maxKey": 1} }
{ "acknowledged": true, "insertedId": 24 }
{ "acknowledged": true, "insertedId": true }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
BinaryResult: {"$binary": {"base64": "MTIzNA==", "subType": "10"}}
BinaryResult: {"$binary": {"base64": "MTIzNA==", "subType": "10"}}
BinaryResult: {"$binary": {"base64": "MTIzNA==", "subType": "04"}}
UUIDResult: 5220b418-8f7d-4cd9-bd27-35b6f8d990c5
BinaryResult: {"$binary": {"base64": "MTIzNA==", "subType": "10"}}
4 changes: 2 additions & 2 deletions packages/java-shell/src/test/resources/literal/BinData.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ db.coll.insertOne({"_id": 1, v: new BinData(16, 'MTIzNA==')})
new BinData(16, 'MTIzNA==')
// command checkResultClass
BinData(16, 'MTIzNA==')
// command checkResultClass
new BinData(4, 'MTIzNA==')
// command checkResultClass dontReplaceId
new BinData(4, 'UiC0GI99TNm9JzW2+NmQxQ==')
// command checkResultClass
db.coll.find().toArray()[0].v
// clear
Expand Down

0 comments on commit 231a95e

Please sign in to comment.