From e495cc46859d5aecc5f5e630fbbb587556ea7b5a Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 26 Feb 2016 11:08:59 -0800 Subject: [PATCH 1/2] add encryption support --- src/js_realm.cpp | 79 ++++++++++++++++--------------- src/object-store/shared_realm.cpp | 3 ++ src/object-store/shared_realm.hpp | 5 ++ tests/lib/encryption-tests.js | 60 +++++++++++++++++++++++ tests/lib/index.js | 1 + 5 files changed, 111 insertions(+), 37 deletions(-) create mode 100644 tests/lib/encryption-tests.js diff --git a/src/js_realm.cpp b/src/js_realm.cpp index 37affeac..2e80f099 100644 --- a/src/js_realm.cpp +++ b/src/js_realm.cpp @@ -31,6 +31,7 @@ #include using namespace realm; +using RJSAccessor = realm::NativeAccessor; class RJSRealmDelegate : public BindingContext { public: @@ -152,48 +153,52 @@ JSObjectRef RealmConstructor(JSContextRef ctx, JSObjectRef constructor, size_t a Realm::Config config; std::map defaults; std::map constructors; - switch (argumentCount) { - case 0: - config.path = RJSDefaultPath(); - break; - case 1: { - JSValueRef value = arguments[0]; - if (JSValueIsString(ctx, value)) { - config.path = RJSValidatedStringForValue(ctx, value, "path"); - break; + if (argumentCount == 0) { + config.path = RJSDefaultPath(); + } + else if (argumentCount == 1) { + JSValueRef value = arguments[0]; + if (JSValueIsString(ctx, value)) { + config.path = RJSValidatedStringForValue(ctx, value, "path"); + } + else if (JSValueIsObject(ctx, value)) { + JSObjectRef object = RJSValidatedValueToObject(ctx, value); + + static JSStringRef pathString = JSStringCreateWithUTF8CString("path"); + JSValueRef pathValue = RJSValidatedPropertyValue(ctx, object, pathString); + if (!JSValueIsUndefined(ctx, pathValue)) { + config.path = RJSValidatedStringForValue(ctx, pathValue, "path"); + } + else { + config.path = RJSDefaultPath(); } - if (JSValueIsObject(ctx, value)) { - JSObjectRef object = RJSValidatedValueToObject(ctx, value); - static JSStringRef pathString = JSStringCreateWithUTF8CString("path"); - JSValueRef pathValue = RJSValidatedPropertyValue(ctx, object, pathString); - if (!JSValueIsUndefined(ctx, pathValue)) { - config.path = RJSValidatedStringForValue(ctx, pathValue, "path"); - } - else { - config.path = RJSDefaultPath(); - } + static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); + JSValueRef schemaValue = RJSValidatedPropertyValue(ctx, object, schemaString); + if (!JSValueIsUndefined(ctx, schemaValue)) { + config.schema.reset(new Schema(RJSParseSchema(ctx, RJSValidatedValueToObject(ctx, schemaValue), defaults, constructors))); + } - static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); - JSValueRef schemaValue = RJSValidatedPropertyValue(ctx, object, schemaString); - if (!JSValueIsUndefined(ctx, schemaValue)) { - config.schema.reset(new Schema(RJSParseSchema(ctx, RJSValidatedValueToObject(ctx, schemaValue), defaults, constructors))); - } - - static JSStringRef schemaVersionString = JSStringCreateWithUTF8CString("schemaVersion"); - JSValueRef versionValue = RJSValidatedPropertyValue(ctx, object, schemaVersionString); - if (JSValueIsNumber(ctx, versionValue)) { - config.schema_version = RJSValidatedValueToNumber(ctx, versionValue); - } - else { - config.schema_version = 0; - } - break; + static JSStringRef schemaVersionString = JSStringCreateWithUTF8CString("schemaVersion"); + JSValueRef versionValue = RJSValidatedPropertyValue(ctx, object, schemaVersionString); + if (JSValueIsNumber(ctx, versionValue)) { + config.schema_version = RJSValidatedValueToNumber(ctx, versionValue); + } + else { + config.schema_version = 0; + } + + static JSStringRef encryptionKeyString = JSStringCreateWithUTF8CString("encryptionKey"); + JSValueRef encryptionKeyValue = RJSValidatedPropertyValue(ctx, object, encryptionKeyString); + if (!JSValueIsUndefined(ctx, encryptionKeyValue)) { + std::string encryptionKey = RJSAccessor::to_binary(ctx, encryptionKeyValue); + config.encryption_key = std::vector(encryptionKey.begin(), encryptionKey.end());; } } - default: - *jsException = RJSMakeError(ctx, "Invalid arguments when constructing 'Realm'"); - return NULL; + } + else { + *jsException = RJSMakeError(ctx, "Invalid arguments when constructing 'Realm'"); + return NULL; } ensure_directory_exists_for_file(config.path); SharedRealm realm = Realm::get_shared_realm(config); diff --git a/src/object-store/shared_realm.cpp b/src/object-store/shared_realm.cpp index 0057a638..3305d937 100644 --- a/src/object-store/shared_realm.cpp +++ b/src/object-store/shared_realm.cpp @@ -72,6 +72,9 @@ Realm::Realm(Config config) m_group = m_read_only_group.get(); } else { + if (m_config.encryption_key.data() && m_config.encryption_key.size() != 64) { + throw InvalidEncryptionKeyException(); + } m_history = realm::make_client_history(m_config.path, m_config.encryption_key.data()); SharedGroup::DurabilityLevel durability = m_config.in_memory ? SharedGroup::durability_MemOnly : SharedGroup::durability_Full; diff --git a/src/object-store/shared_realm.hpp b/src/object-store/shared_realm.hpp index 394686ce..536495ae 100644 --- a/src/object-store/shared_realm.hpp +++ b/src/object-store/shared_realm.hpp @@ -199,6 +199,11 @@ namespace realm { public: UnitializedRealmException(std::string message) : std::runtime_error(message) {} }; + + class InvalidEncryptionKeyException : public std::runtime_error { + public: + InvalidEncryptionKeyException() : std::runtime_error("Encryption key must be 64 bytes.") {} + }; } #endif /* defined(REALM_REALM_HPP) */ diff --git a/tests/lib/encryption-tests.js b/tests/lib/encryption-tests.js new file mode 100644 index 00000000..651f98ff --- /dev/null +++ b/tests/lib/encryption-tests.js @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +'use strict'; + +var Realm = require('realm'); +var BaseTest = require('./base-test'); +var TestCase = require('./asserts'); +var Schemas = require('./schemas'); + +module.exports = BaseTest.extend({ + testEncryptedInvalidKeys: function() { + // test failure with invalid keys + TestCase.assertThrows(function() { + new Realm({schema: [Schemas.TestObject], encryptionKey: " ".repeat(64)}); + }, "Encryption Key must be an ArrayBuffer"); + + TestCase.assertThrows(function() { + new Realm({schema: [Schemas.TestObject], encryptionKey: new Int8Array(63)}); + }, "Encryption Key must be 64 byes"); + }, + testEncryptionValidKey: function() { + var key = new Int8Array(64); + key[0] = 1; + var realm = new Realm({schema: [Schemas.TestObject], encryptionKey: key}); + + realm.write(function() { + realm.create('TestObject', {doubleCol: 1}); + TestCase.assertEqual(realm.objects('TestObject').length, 1); + }); + + // test failure with different or missing + realm.close(); + TestCase.assertThrows(function() { + new Realm({schema: [Schemas.TestObject], encryptionKey: new Int8Array(64)}); + }); + TestCase.assertThrows(function() { + new Realm({schema: [Schemas.TestObject]}); + }); + + // test can reopen with original key + var realm = new Realm({schema: [Schemas.TestObject], encryptionKey: key}); + TestCase.assertEqual(realm.objects('TestObject').length, 1); + }, +}); diff --git a/tests/lib/index.js b/tests/lib/index.js index 19cc7767..c8b63a06 100644 --- a/tests/lib/index.js +++ b/tests/lib/index.js @@ -24,6 +24,7 @@ var TESTS = { RealmTests: require('./realm-tests'), ResultsTests: require('./results-tests'), QueryTests: require('./query-tests'), + QueryTests: require('./encryption-tests'), }; var SPECIAL_METHODS = { From 4177b0fb4038d76edbb4f82930297e3ba3d36722 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 26 Feb 2016 11:18:47 -0800 Subject: [PATCH 2/2] fix for test naming --- tests/lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/index.js b/tests/lib/index.js index c8b63a06..7c63bb2b 100644 --- a/tests/lib/index.js +++ b/tests/lib/index.js @@ -24,7 +24,7 @@ var TESTS = { RealmTests: require('./realm-tests'), ResultsTests: require('./results-tests'), QueryTests: require('./query-tests'), - QueryTests: require('./encryption-tests'), + EncryptionTests: require('./encryption-tests'), }; var SPECIAL_METHODS = {