diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h index b50c58ce1e28f2bfb1fed6c42bebcaf21bcfb66e..b43045af04205a0307e5014a2ce347dfed8cb49f 100644 --- a/include/grpc/grpc_security.h +++ b/include/grpc/grpc_security.h @@ -147,6 +147,29 @@ GRPC_API grpc_channel_credentials *grpc_google_default_credentials_create(void); #define GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR \ "GRPC_DEFAULT_SSL_ROOTS_FILE_PATH" +/* Results for the SSL roots override callback. */ +typedef enum { + GRPC_SSL_ROOTS_OVERRIDE_OK, + GRPC_SSL_ROOTS_OVERRIDE_FAIL_PERMANENTLY, /* Do not try fallback options. */ + GRPC_SSL_ROOTS_OVERRIDE_FAIL +} grpc_ssl_roots_override_result; + +/* Callback for getting the SSL roots override from the application. + In case of success, *pem_roots_certs must be set to a NULL terminated string + containing the list of PEM encoded root certificates. The ownership is passed + to the core and freed (laster by the core) with gpr_free. + If this function fails and GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment is + set to a valid path, it will override the roots specified this func */ +typedef grpc_ssl_roots_override_result (*grpc_ssl_roots_override_callback)( + char **pem_root_certs); + +/* Setup a callback to override the default TLS/SSL roots. + This function is not thread-safe and must be called at initialization time + before any ssl credentials are created to have the desired side effect. + If GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment is set to a valid path, the + callback will not be called. */ +void grpc_set_ssl_roots_override_callback(grpc_ssl_roots_override_callback cb); + /* Object that holds a private key / certificate chain pair in PEM format. */ typedef struct { /* private_key is the NULL-terminated string containing the PEM encoding of @@ -163,8 +186,9 @@ typedef struct { of the server root certificates. If this parameter is NULL, the implementation will first try to dereference the file pointed by the GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable, and if that fails, - get the roots from a well-known place on disk (in the grpc install - directory). + try to get the roots set by grpc_override_ssl_default_roots. Eventually, + if all these fail, it will try to get the roots from a well-known place on + disk (in the grpc install directory). - pem_key_cert_pair is a pointer on the object containing client's private key and certificate chain. This parameter can be NULL if the client does not have such a key/cert pair. */ diff --git a/src/core/security/security_connector.c b/src/core/security/security_connector.c index 61336a1057df088fc9bfcb740fe4e4de4628010c..bdccbabfea3458a4cd2e912aecebe1cd8d610af3 100644 --- a/src/core/security/security_connector.c +++ b/src/core/security/security_connector.c @@ -61,6 +61,14 @@ static const char *installed_roots_path = INSTALL_PREFIX "/share/grpc/roots.pem"; #endif +/* -- Overridden default roots. -- */ + +static grpc_ssl_roots_override_callback ssl_roots_override_cb = NULL; + +void grpc_set_ssl_roots_override_callback(grpc_ssl_roots_override_callback cb) { + ssl_roots_override_cb = cb; +} + /* -- Cipher suites. -- */ /* Defines the cipher suites that we accept by default. All these cipher suites @@ -595,23 +603,44 @@ static grpc_security_connector_vtable ssl_channel_vtable = { static grpc_security_connector_vtable ssl_server_vtable = { ssl_server_destroy, ssl_server_do_handshake, ssl_server_check_peer}; -static gpr_slice default_pem_root_certs; +static gpr_slice compute_default_pem_root_certs_once(void) { + gpr_slice result = gpr_empty_slice(); -static void init_default_pem_root_certs(void) { /* First try to load the roots from the environment. */ char *default_root_certs_path = gpr_getenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR); - if (default_root_certs_path == NULL) { - default_pem_root_certs = gpr_empty_slice(); - } else { - default_pem_root_certs = gpr_load_file(default_root_certs_path, 0, NULL); + if (default_root_certs_path != NULL) { + result = gpr_load_file(default_root_certs_path, 0, NULL); gpr_free(default_root_certs_path); } + /* Try overridden roots if needed. */ + grpc_ssl_roots_override_result ovrd_res = GRPC_SSL_ROOTS_OVERRIDE_FAIL; + if (GPR_SLICE_IS_EMPTY(result) && ssl_roots_override_cb != NULL) { + char *pem_root_certs = NULL; + ovrd_res = ssl_roots_override_cb(&pem_root_certs); + if (ovrd_res == GRPC_SSL_ROOTS_OVERRIDE_OK) { + GPR_ASSERT(pem_root_certs != NULL); + result = gpr_slice_new(pem_root_certs, strlen(pem_root_certs), gpr_free); + } + } + /* Fall back to installed certs if needed. */ - if (GPR_SLICE_IS_EMPTY(default_pem_root_certs)) { - default_pem_root_certs = gpr_load_file(installed_roots_path, 0, NULL); + if (GPR_SLICE_IS_EMPTY(result) && + ovrd_res != GRPC_SSL_ROOTS_OVERRIDE_FAIL_PERMANENTLY) { + result = gpr_load_file(installed_roots_path, 0, NULL); } + return result; +} + +static gpr_slice default_pem_root_certs; + +static void init_default_pem_root_certs(void) { + default_pem_root_certs = compute_default_pem_root_certs_once(); +} + +gpr_slice grpc_get_default_ssl_roots_for_testing(void) { + return compute_default_pem_root_certs_once(); } size_t grpc_get_default_ssl_roots(const unsigned char **pem_root_certs) { diff --git a/src/core/security/security_connector.h b/src/core/security/security_connector.h index 2b734109b389422100d81bbe6ae85e5c831093cc..39df7821f0c72c79aab3f11e75d96773e809aeb2 100644 --- a/src/core/security/security_connector.h +++ b/src/core/security/security_connector.h @@ -209,6 +209,9 @@ grpc_security_status grpc_ssl_channel_security_connector_create( /* Gets the default ssl roots. */ size_t grpc_get_default_ssl_roots(const unsigned char **pem_root_certs); +/* Exposed for TESTING ONLY!. */ +gpr_slice grpc_get_default_ssl_roots_for_testing(void); + /* Config for ssl servers. */ typedef struct { unsigned char **pem_private_keys; diff --git a/test/core/security/security_connector_test.c b/test/core/security/security_connector_test.c index 0dcffa40ce413b7a69125a355bb4276e02e20583..ee5435f01ddccf287fdc3beee8dc119216541742 100644 --- a/test/core/security/security_connector_test.c +++ b/test/core/security/security_connector_test.c @@ -1,6 +1,6 @@ /* * - * Copyright 2015, Google Inc. + * Copyright 2015-2016, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,9 @@ #include "src/core/security/security_connector.h" #include "src/core/security/security_context.h" +#include "src/core/support/env.h" +#include "src/core/support/file.h" +#include "src/core/support/string.h" #include "src/core/tsi/ssl_transport_security.h" #include "src/core/tsi/transport_security.h" #include "test/core/util/test_config.h" @@ -44,6 +47,7 @@ #include <grpc/support/alloc.h> #include <grpc/support/log.h> +#include <grpc/support/string_util.h> #include <grpc/support/useful.h> static int check_transport_security_type(const grpc_auth_context *ctx) { @@ -297,7 +301,66 @@ static void test_cn_and_multiple_sans_and_others_ssl_peer_to_auth_context( GRPC_AUTH_CONTEXT_UNREF(ctx, "test"); } -/* TODO(jboeuf): Unit-test tsi_shallow_peer_from_auth_context. */ +static const char *roots_for_override_api = "roots for override api"; + +static grpc_ssl_roots_override_result override_roots_success( + char **pem_root_certs) { + *pem_root_certs = gpr_strdup(roots_for_override_api); + return GRPC_SSL_ROOTS_OVERRIDE_OK; +} + +static grpc_ssl_roots_override_result override_roots_permanent_failure( + char **pem_root_certs) { + return GRPC_SSL_ROOTS_OVERRIDE_FAIL_PERMANENTLY; +} + +static void test_default_ssl_roots(void) { + const char *roots_for_env_var = "roots for env var"; + + char *roots_env_var_file_path; + FILE *roots_env_var_file = + gpr_tmpfile("test_roots_for_env_var", &roots_env_var_file_path); + fwrite(roots_for_env_var, 1, strlen(roots_for_env_var), roots_env_var_file); + fclose(roots_env_var_file); + + /* First let's get the root through the override: set the env to an invalid + value. */ + gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, ""); + grpc_set_ssl_roots_override_callback(override_roots_success); + gpr_slice roots = grpc_get_default_ssl_roots_for_testing(); + char *roots_contents = gpr_dump_slice(roots, GPR_DUMP_ASCII); + gpr_slice_unref(roots); + GPR_ASSERT(strcmp(roots_contents, roots_for_override_api) == 0); + gpr_free(roots_contents); + + /* Now let's set the env var: We should get the contents pointed value + instead. */ + gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_env_var_file_path); + roots = grpc_get_default_ssl_roots_for_testing(); + roots_contents = gpr_dump_slice(roots, GPR_DUMP_ASCII); + gpr_slice_unref(roots); + GPR_ASSERT(strcmp(roots_contents, roots_for_env_var) == 0); + gpr_free(roots_contents); + + /* Now reset the env var. We should fall back to the value overridden using + the api. */ + gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, ""); + roots = grpc_get_default_ssl_roots_for_testing(); + roots_contents = gpr_dump_slice(roots, GPR_DUMP_ASCII); + gpr_slice_unref(roots); + GPR_ASSERT(strcmp(roots_contents, roots_for_override_api) == 0); + gpr_free(roots_contents); + + /* Now setup a permanent failure for the overridden roots and we should get + an empty slice. */ + grpc_set_ssl_roots_override_callback(override_roots_permanent_failure); + roots = grpc_get_default_ssl_roots_for_testing(); + GPR_ASSERT(GPR_SLICE_IS_EMPTY(roots)); + + /* Cleanup. */ + remove(roots_env_var_file_path); + gpr_free(roots_env_var_file_path); +} int main(int argc, char **argv) { grpc_test_init(argc, argv); @@ -308,6 +371,7 @@ int main(int argc, char **argv) { test_cn_and_one_san_ssl_peer_to_auth_context(); test_cn_and_multiple_sans_ssl_peer_to_auth_context(); test_cn_and_multiple_sans_and_others_ssl_peer_to_auth_context(); + test_default_ssl_roots(); grpc_shutdown(); return 0;