Merge pull request #1790 from realm/yg/new-ssl-config-api

Group SSL configuration options in a new config object
This commit is contained in:
Kenneth Geisshirt 2018-05-16 11:47:25 +02:00 committed by GitHub
commit f311fc7ba5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 140 additions and 78 deletions

View File

@ -1,4 +1,4 @@
X.Y.Z Release notes () x.y.z Release notes (TBD)
============================================================= =============================================================
### Compatibility ### Compatibility
* Sync protocol: 24 * Sync protocol: 24
@ -10,10 +10,12 @@ X.Y.Z Release notes ()
* None. * None.
### Enhancements ### Enhancements
* [Sync] The SSL configuration options are now grouped in a new config object. (#1465)
* [Sync] The Adapter can accept a new config parameter that specifies SSL settings for spawned sync sessions.
* Added `Object.linkingObjectsCount()` method, that returns total count of incoming links. * Added `Object.linkingObjectsCount()` method, that returns total count of incoming links.
### Bug fixes ### Bug fixes
* None * None.
### Internal ### Internal
* None. * None.

View File

@ -347,52 +347,7 @@ Realm.defaultPath;
* If omitted, the schema will be read from the existing Realm file. * If omitted, the schema will be read from the existing Realm file.
* @property {number} [schemaVersion] - **Required** (and must be incremented) after * @property {number} [schemaVersion] - **Required** (and must be incremented) after
* changing the `schema`. * changing the `schema`.
* @property {Object} [sync] - Sync configuration parameters with the following * @property {Realm.Sync~SyncConfiguration} [sync] - Sync configuration parameters.
* 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.
* The `error` callback can take up to five optional arguments: `name`, `message`, `isFatal`,
* `category`, and `code`.
* - `validate_ssl` - Indicating if SSL certificates must be validated
* - `ssl_trust_certificate_path` - A path where to find trusted SSL certificates
* - `open_ssl_verify_callback` - A callback function used to accept or reject the server's
* SSL certificate. open_ssl_verify_callback is called with an object of type
* <code>
* {
* serverAddress: String,
* serverPort: Number,
* pemCertificate: String,
* acceptedByOpenSSL: Boolean,
* depth: Number
* }
* </code>
* The return value of open_ssl_verify_callback decides whether the certificate is accepted (true)
* or rejected (false). The open_ssl_verify_callback function is only respected on platforms where
* OpenSSL is used for the sync client, e.g. Linux. The open_ssl_verify_callback function is not
* allowed to throw exceptions. If the operations needed to verify the certificate lead to an exception,
* the exception must be caught explicitly before returning. The return value would typically be false
* in case of an exception.
*
* When the sync client has received the server's certificate chain, it presents every certificate in
* the chain to the open_ssl_verify_callback function. The depth argument specifies the position of the
* certificate in the chain. depth = 0 represents the actual server certificate. The root
* certificate has the highest depth. The certificate of highest depth will be presented first.
*
* acceptedByOpenSSL is true if OpenSSL has accepted the certificate, and false if OpenSSL has rejected it.
* It is generally safe to return true when acceptedByOpenSSL is true. If acceptedByOpenSSL is false, an
* independent verification should be made.
*
* One possible way of using the open_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 open_ssl_verify_callback is to enable custom certificate handling and to solve cases where
* OpenSSL erroneously rejects valid certificates possibly because OpenSSL doesn't have access to the
* proper trust certificates.
* - `partial` - Whether this Realm should be opened in 'partial synchronization' mode.
* Partial synchronisation only synchronizes those objects that match the query specified in contrast
* to the normal mode of operation that synchronises all objects in a remote Realm.
* **Partial synchronization is a tech preview. Its APIs are subject to change.**
*/ */
/** /**

View File

@ -16,6 +16,68 @@
// //
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
/**
* This describes the different options used to create a {@link Realm} instance with Realm Platform synchronization.
* @typedef {Object} Realm.Sync~SyncConfiguration
* @property {Realm.Sync.User} user - A {@link Realm.Sync.User} object obtained by calling `Realm.Sync.User.login`.
* @property {string} url - A `string` which contains a valid Realm Sync url.
* @property {function} [error] - A callback function which is called in error situations.
* The `error` callback can take up to five optional arguments: `name`, `message`, `isFatal`,
* `category`, and `code`.
*
* @deprecated
* @property {boolean} [validate_ssl] - Indicating if SSL certificates must be validated.
* @deprecated
* @property {string} [ssl_trust_certificate_path] - A path where to find trusted SSL certificates.
* @deprecated
* @property {Realm.Sync~sslValidateCallback} [open_ssl_verify_callback] - A callback function used to
* accept or reject the server's SSL certificate.
*
* @property {Realm.Sync~SSLConfiguration} [ssl] - SSL configuration.
* @property {boolean} [partial] - Whether this Realm should be opened in 'partial synchronization' mode.
* Partial synchronisation only synchronizes those objects that match the query specified in contrast
* to the normal mode of operation that synchronises all objects in a remote Realm.
* **Partial synchronization is a tech preview. Its APIs are subject to change.**
*/
/**
* This describes the different options used to create a {@link Realm} instance with Realm Platform synchronization.
* @typedef {Object} Realm.Sync~SSLConfiguration
* @property {boolean} validate - Indicating if SSL certificates must be validated. Default is `true`.
* @property {string} certificatePath - A path where to find trusted SSL certificates.
* @property {Realm.Sync~sslValidateCallback} validateCallback - A callback function used to
* accept or reject the server's SSL certificate.
*/
/**
* When the sync client has received the server's certificate chain, it presents every certificate in
* the chain to the {@link Realm.Sync~sslValidateCallback} callback.
*
* The return value of the callback decides whether the certificate is accepted (`true`)
* or rejected (`false`). {@link Realm.Sync~sslValidateCallback} is only respected on platforms where
* OpenSSL is used for the sync client, e.g. Linux. The callback is not
* allowed to throw exceptions. If the operations needed to verify the certificate lead to an exception,
* the exception must be caught explicitly before returning. The return value would typically be false
* in case of an exception.
* @callback Realm.Sync~sslValidateCallback
* @param {Realm.Sync~SSLCertificateValidationInfo} validationInfo
* @return {boolean}
*/
/**
* @typedef {Object} Realm.Sync~SSLCertificateValidationInfo
* @property {string} serverAddress
* @property {number} serverPort
* @property {string} pemCertificate
* @property {boolean} acceptedByOpenSSL - `true` if OpenSSL has accepted the certificate,
* and `false` if OpenSSL has rejected it.
* It is generally safe to return true when `acceptedByOpenSSL` is `true`. If `acceptedByOpenSSL` is `false`,
* an independent verification should be made.
* @property {number} depth - Specifies the position of the certificate in the chain.
* `depth = 0` represents the actual server certificate. The root
* certificate has the highest depth. The certificate of highest depth will be presented first.
*/
/** /**
* When opening a Realm created with Realm Mobile Platform v1.x, it is automatically * When opening a Realm created with Realm Mobile Platform v1.x, it is automatically
* migrated to the v2.x format. In case this migration * migrated to the v2.x format. In case this migration
@ -658,8 +720,9 @@ class Adapter {
* use `.*` to match all all Realms * use `.*` to match all all Realms
* @param {function(realmPath)} changeCallback - called when a new transaction is available * @param {function(realmPath)} changeCallback - called when a new transaction is available
* to process for the given realm_path * to process for the given realm_path
* @param {Realm.Sync~SSLConfiguration} [ssl] - SSL configuration for the spawned sync sessions.
*/ */
constructor(localPath, serverUrl, adminUser, regex, changeCallback) {} constructor(localPath, serverUrl, adminUser, regex, changeCallback, ssl) {}
/** /**
* Get the Array of current instructions for the given Realm. * Get the Array of current instructions for the given Realm.

13
lib/index.d.ts vendored
View File

@ -392,12 +392,22 @@ declare namespace Realm.Sync {
type ErrorCallback = (session: Session, error: SyncError) => void; type ErrorCallback = (session: Session, error: SyncError) => void;
type SSLVerifyCallback = (sslVerifyObject: SSLVerifyObject) => boolean; type SSLVerifyCallback = (sslVerifyObject: SSLVerifyObject) => boolean;
interface SSLConfiguration {
validate?: boolean;
certificatePath?: string;
validateCallback?: SSLVerifyCallback;
}
interface SyncConfiguration { interface SyncConfiguration {
user: User; user: User;
url: string; url: string;
/** @deprecated use `ssl` instead */
validate_ssl?: boolean; validate_ssl?: boolean;
/** @deprecated use `ssl` instead */
ssl_trust_certificate_path?: string; ssl_trust_certificate_path?: string;
/** @deprecated use `ssl` instead */
open_ssl_verify_callback?: SSLVerifyCallback; open_ssl_verify_callback?: SSLVerifyCallback;
ssl?: SSLConfiguration;
error?: ErrorCallback; error?: ErrorCallback;
partial?: boolean; partial?: boolean;
_disablePartialSyncUrlChecks?:boolean; _disablePartialSyncUrlChecks?:boolean;
@ -496,7 +506,8 @@ declare namespace Realm.Sync {
server_url: string, server_url: string,
admin_user: User, admin_user: User,
regex: string, regex: string,
change_callback: Function change_callback: Function,
ssl?: SSLConfiguration
) )
/** /**

View File

@ -716,6 +716,7 @@ public:
// private // private
static std::function<SyncBindSessionHandler> session_bind_callback(ContextType ctx, ObjectType sync_constructor); static std::function<SyncBindSessionHandler> session_bind_callback(ContextType ctx, ObjectType sync_constructor);
static void populate_sync_config(ContextType, ObjectType realm_constructor, ObjectType config_object, Realm::Config&); static void populate_sync_config(ContextType, ObjectType realm_constructor, ObjectType config_object, Realm::Config&);
static void populate_sync_config_for_ssl(ContextType, ObjectType config_object, SyncConfig&);
// static properties // static properties
static void get_is_developer_edition(ContextType, ObjectType, ReturnValue &); static void get_is_developer_edition(ContextType, ObjectType, ReturnValue &);
@ -831,33 +832,7 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
SSLVerifyCallbackSyncThreadFunctor<T> ssl_verify_functor {ctx, Value::validated_to_function(ctx, ssl_verify_func)}; SSLVerifyCallbackSyncThreadFunctor<T> ssl_verify_functor {ctx, Value::validated_to_function(ctx, ssl_verify_func)};
ssl_verify_callback = std::move(ssl_verify_functor); ssl_verify_callback = std::move(ssl_verify_functor);
} }
#if REALM_ANDROID
// For React Native Android, if the user didn't define the ssl_verify_callback, we provide a default
// implementation for him, otherwise all SSL validation will fail, since the Sync client doesn't have
// access to the Android Keystore.
// This default implementation will perform a JNI call to invoke a Java method defined at the `SSLHelper`
// to perform the certificate verification.
else {
auto ssl_verify_functor =
[](const std::string server_address, realm::sync::Session::port_type server_port,
const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
JNIEnv* env = realm::jni_util::JniUtils::get_env(true);
static jmethodID java_certificate_verifier = env->GetStaticMethodID(ssl_helper_class, "certificateVerifier", "(Ljava/lang/String;Ljava/lang/String;I)Z");
jstring jserver_address = env->NewStringUTF(server_address.c_str());
// deep copy the pem_data into a string so DeleteLocalRef delete the local reference not the original const char
std::string pem(pem_data, pem_size);
jstring jpem = env->NewStringUTF(pem.c_str());
bool isValid = env->CallStaticBooleanMethod(ssl_helper_class, java_certificate_verifier,
jserver_address,
jpem, depth) == JNI_TRUE;
env->DeleteLocalRef(jserver_address);
env->DeleteLocalRef(jpem);
return isValid;
};
ssl_verify_callback = std::move(ssl_verify_functor);
}
#endif
bool is_partial = false; bool is_partial = false;
ValueType partial_value = Object::get_property(ctx, sync_config_object, "partial"); ValueType partial_value = Object::get_property(ctx, sync_config_object, "partial");
if (!Value::is_undefined(ctx, partial_value)) { if (!Value::is_undefined(ctx, partial_value)) {
@ -879,10 +854,18 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
} }
config.sync_config->bind_session_handler = std::move(bind); config.sync_config->bind_session_handler = std::move(bind);
config.sync_config->error_handler = std::move(error_handler); config.sync_config->error_handler = std::move(error_handler);
config.sync_config->is_partial = is_partial;
// TODO: remove
config.sync_config->client_validate_ssl = client_validate_ssl; config.sync_config->client_validate_ssl = client_validate_ssl;
config.sync_config->ssl_trust_certificate_path = ssl_trust_certificate_path; config.sync_config->ssl_trust_certificate_path = ssl_trust_certificate_path;
config.sync_config->ssl_verify_callback = std::move(ssl_verify_callback); config.sync_config->ssl_verify_callback = std::move(ssl_verify_callback);
config.sync_config->is_partial = is_partial;
ValueType ssl_config_value = Object::get_property(ctx, sync_config_object, "ssl");
if (Value::is_object(ctx, ssl_config_value)) {
auto ssl_config_object = Value::to_object(ctx, ssl_config_value);
populate_sync_config_for_ssl(ctx, ssl_config_object, *config.sync_config);
}
config.schema_mode = SchemaMode::Additive; config.schema_mode = SchemaMode::Additive;
config.path = syncManagerShared().path_for_realm(*shared_user, config.sync_config->realm_url()); config.path = syncManagerShared().path_for_realm(*shared_user, config.sync_config->realm_url());
@ -891,7 +874,55 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
config.sync_config->realm_encryption_key = std::array<char, 64>(); config.sync_config->realm_encryption_key = std::array<char, 64>();
std::copy_n(config.encryption_key.begin(), config.sync_config->realm_encryption_key->size(), config.sync_config->realm_encryption_key->begin()); std::copy_n(config.encryption_key.begin(), config.sync_config->realm_encryption_key->size(), config.sync_config->realm_encryption_key->begin());
} }
#if REALM_ANDROID
// For React Native Android, if the user didn't define the ssl_verify_callback, we provide a default
// implementation for him, otherwise all SSL validation will fail, since the Sync client doesn't have
// access to the Android Keystore.
// This default implementation will perform a JNI call to invoke a Java method defined at the `SSLHelper`
// to perform the certificate verification.
if (!config.sync_config->ssl_verify_callback) {
auto ssl_verify_functor =
[](const std::string server_address, realm::sync::Session::port_type server_port,
const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
JNIEnv* env = realm::jni_util::JniUtils::get_env(true);
static jmethodID java_certificate_verifier = env->GetStaticMethodID(ssl_helper_class, "certificateVerifier", "(Ljava/lang/String;Ljava/lang/String;I)Z");
jstring jserver_address = env->NewStringUTF(server_address.c_str());
// deep copy the pem_data into a string so DeleteLocalRef delete the local reference not the original const char
std::string pem(pem_data, pem_size);
jstring jpem = env->NewStringUTF(pem.c_str());
bool isValid = env->CallStaticBooleanMethod(ssl_helper_class, java_certificate_verifier,
jserver_address,
jpem, depth) == JNI_TRUE;
env->DeleteLocalRef(jserver_address);
env->DeleteLocalRef(jpem);
return isValid;
};
config.sync_config->ssl_verify_callback = std::move(ssl_verify_functor);
}
#endif
} }
} }
template<typename T>
void SyncClass<T>::populate_sync_config_for_ssl(ContextType ctx, ObjectType config_object, SyncConfig& config)
{
ValueType validate_ssl = Object::get_property(ctx, config_object, "validate");
if (Value::is_boolean(ctx, validate_ssl)) {
config.client_validate_ssl = Value::to_boolean(ctx, validate_ssl);
}
ValueType certificate_path = Object::get_property(ctx, config_object, "certificatePath");
if (Value::is_string(ctx, certificate_path)) {
config.ssl_trust_certificate_path = std::string(Value::to_string(ctx, certificate_path));
}
ValueType validate_callback = Object::get_property(ctx, config_object, "validateCallback");
if (Value::is_function(ctx, validate_callback)) {
config.ssl_verify_callback = SSLVerifyCallbackSyncThreadFunctor<T> { ctx, Value::to_function(ctx, validate_callback) };
}
}
} // js } // js
} // realm } // realm