From c1e198cbabbfd5746f4f1b14c698ff63887f22bd Mon Sep 17 00:00:00 2001 From: Morten Krogh Date: Fri, 22 Sep 2017 11:17:59 +0200 Subject: [PATCH] Documentation and comments for ssl_verify_callback. --- CHANGELOG.md | 8 ++++++++ docs/realm.js | 53 +++++++++++++++++++++++++++++++++++++++---------- src/js_sync.hpp | 20 ++++++++++++++----- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b04281cc..371982d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +NEXT RELEASE +============================================================= + +### Enhancements +* Add a callback function used to verify SSL certificates in + the sync config. + + 2.0.0-rc10 Release notes (2017-9-19) ============================================================= ### Breaking changes diff --git a/docs/realm.js b/docs/realm.js index 63c14ad5..179c522b 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -92,17 +92,17 @@ class Realm { constructor(config) {} /** - * Open a realm asynchronously with a promise. If the realm is synced, it will be fully + * Open a realm asynchronously with a promise. If the realm is synced, it will be fully * synchronized before it is available. - * @param {Realm~Configuration} config + * @param {Realm~Configuration} config * @returns {ProgressPromise} - a promise that will be resolved with the realm instance when it's available. */ static open(config) {} /** - * Open a realm asynchronously with a callback. If the realm is synced, it will be fully + * Open a realm asynchronously with a callback. If the realm is synced, it will be fully * synchronized before it is available. - * @param {Realm~Configuration} config + * @param {Realm~Configuration} config * @param {callback(error, realm)} - will be called when the realm is ready. * @param {callback(transferred, transferable)} [progressCallback] - an optional callback for download progress notifications * @throws {Error} If anything in the provided `config` is invalid. @@ -217,7 +217,7 @@ class Realm { /* * Replaces all string columns in this Realm with a string enumeration column and compacts the * database file. - * + * * Cannot be called from a write transaction. * * Compaction will not occur if other `Realm` instances exist. @@ -269,12 +269,12 @@ Realm.defaultPath; * This function takes two arguments: * - `oldRealm` - The Realm before migration is performed. * - `newRealm` - The Realm that uses the latest `schema`, which should be modified as necessary. - * @property {callback(number, number)} [shouldCompactOnLaunch] - The function called when opening - * a Realm for the first time during the life of a process to determine if it should be compacted + * @property {callback(number, number)} [shouldCompactOnLaunch] - The function called when opening + * a Realm for the first time during the life of a process to determine if it should be compacted * before being returned to the user. The function takes two arguments: - * - `totalSize` - The total file size (data + free space) + * - `totalSize` - The total file size (data + free space) * - `unusedSize` - The total bytes used by data in the file. - * It returns `true` to indicate that an attempt to compact the file should be made. The compaction + * It returns `true` to indicate that an attempt to compact the file should be made. The compaction * will be skipped if another process is accessing it. * @property {string} [path={@link Realm.defaultPath}] - The path to the file where the * Realm database should be stored. @@ -288,13 +288,44 @@ Realm.defaultPath; * object types in this Realm. **Required** when first creating a Realm at this `path`. * @property {number} [schemaVersion] - **Required** (and must be incremented) after * changing the `schema`. - * @property {Object} [sync] - Sync configuration parameters with the following + * @property {Object} [sync] - Sync configuration parameters with the following * child properties: * - `user` - A `User` object obtained by calling `Realm.Sync.User.login` * - `url` - A `string` which contains a valid Realm Sync url * - `error` - A callback function which is called in error situations * - `validate_ssl` - Indicating if SSL certificates must be validated * - `ssl_trust_certificate_path` - A path where to find trusted SSL certificates + * - `ssl_verify_callback` - A callback function used to accept or reject the server's + * SSL certificate. ssl_verify_callback is called with an object of type + * { + * serverAddress: String + * serverPort: Number + * pemCertificate: String + * preverifyOk: Number + * depth: Number + * } + * + * The return value of ssl_verify_callback decides whether the certificate is accepted (true) + * or rejected (false). The ssl_verify_callback function is only respected on platforms where + * OpenSSL is used for the sync client, e.g. Linux. + * + * When the sync client has received the server's certificate chain, it presents every certificate in + * the chain to the ssl_verify_callback function. The depth argument specifies the position of the + * certificate in the chain. depth = 0 represents the actual server certificate and the root + * certificate has the highest depth. The certificate of highest depth will be presented first. + * + * preverifyOk is 1 if OpenSSL has accepted the certificate, and + * 0 if OpenSSL has rejected it. It is generally safe to return true when preverifyOk is 1. If + * preverifyOk is 0, an independent verification should be made. + * + * One possible way of using the ssl_verify_callback function is to embed the known server certificate + * in the client and accept the presented certificate if and only if it is equal to the known + * certificate. + * + * The purpose of ssl_verify_callback is to enable custom certificate pinning and to solve cases where + * OpenSSL erroneously rejects valid certificates possibly because OpenSSL doesn't have access to the + * proper trust certificates. + * * The `error` callback can take up to four optional arguments: `message`, `isFatal`, `category`, and `code`. */ @@ -329,7 +360,7 @@ Realm.defaultPath; * otherwise specified. * @property {boolean} [optional] - Signals if this property may be assigned `null` or `undefined`. * @property {boolean} [indexed] - Signals if this property should be indexed. Only supported for - * `"string"`, `"int"`, and `"bool"` properties. + * `"string"`, `"int"`, and `"bool"` properties. */ /** diff --git a/src/js_sync.hpp b/src/js_sync.hpp index 6d19bb81..c31948b4 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -243,6 +243,9 @@ private: }; +// An object of type SSLVerifyCallbackSyncThreadFunctor is registered with the sync client in order +// to verify SSL certificates. The SSLVerifyCallbackSyncThreadFunctor object's operator() is called +// on the sync client's event loop thread. template class SSLVerifyCallbackSyncThreadFunctor { public: @@ -255,6 +258,7 @@ public: { } + // This function is called on the sync client's event loop thread. bool operator ()(const std::string& server_address, sync::Session::port_type server_port, const char* pem_data, size_t pem_size, int preverify_ok, int depth) { const std::string pem_certificate {pem_data, pem_size}; @@ -264,10 +268,13 @@ public: m_ssl_certificate_callback_done = false; } + // Dispatch the call to the main_loop_handler on the node.js thread. m_event_loop_dispatcher(this, server_address, server_port, pem_certificate, preverify_ok, depth); bool ssl_certificate_accepted = false; { + // Wait for the return value of the callback function on the node.js main thread. + // The sync client blocks during this wait. std::unique_lock lock(*m_mutex); m_cond_var->wait(lock, [this] { return this->m_ssl_certificate_callback_done; }); ssl_certificate_accepted = m_ssl_certificate_accepted; @@ -276,6 +283,9 @@ public: return ssl_certificate_accepted; } + // main_loop_handler is called on the node.js main thread. + // main_loop_handler calls the user callback (m_func) and sends the return value + // back to the sync client's event loop thread through a condition variable. static void main_loop_handler(SSLVerifyCallbackSyncThreadFunctor* this_object, const std::string& server_address, sync::Session::port_type server_port, @@ -416,7 +426,7 @@ void SessionClass::add_progress_notification(ContextType ctx, FunctionType, O validate_argument_count(argc, 3); if (auto session = get_internal>(this_object)->lock()) { - + std::string direction = Value::validated_to_string(ctx, arguments[0], "direction"); std::string mode = Value::validated_to_string(ctx, arguments[1], "mode"); SyncSession::NotifierType notifierType; @@ -446,20 +456,20 @@ void SessionClass::add_progress_notification(ContextType ctx, FunctionType, O Protected protected_callback(ctx, callback_function); Protected protected_this(ctx, this_object); Protected protected_ctx(Context::get_global_context(ctx)); - std::function progressFunc; + std::function progressFunc; EventLoopDispatcher progress_handler([=](uint64_t transferred_bytes, uint64_t transferrable_bytes) { HANDLESCOPE ValueType callback_arguments[2]; callback_arguments[0] = Value::from_number(protected_ctx, transferred_bytes); callback_arguments[1] = Value::from_number(protected_ctx, transferrable_bytes); - + Function::callback(protected_ctx, protected_callback, typename T::Object(), 2, callback_arguments); }); progressFunc = std::move(progress_handler); - + auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, false); auto syncSession = create_object>(ctx, new WeakSession(session)); @@ -595,7 +605,7 @@ void SyncClass::populate_sync_config(ContextType ctx, ObjectType realm_constr static std::regex tilde("/~/"); raw_realm_url = std::regex_replace(raw_realm_url, tilde, "/__auth/"); } - + bool client_validate_ssl = true; ValueType validate_ssl_temp = Object::get_property(ctx, sync_config_object, "validate_ssl"); if (!Value::is_undefined(ctx, validate_ssl_temp)) {