From aaebf7ae7467a43ba69f27943069613f23808461 Mon Sep 17 00:00:00 2001
From: Julien Boeuf <jboeuf@google.com>
Date: Thu, 28 Jan 2016 17:04:42 -0800
Subject: [PATCH] Changing the API to use a callback mechanism.

This is the agreed-upon solution.
---
 include/grpc/grpc_security.h                 | 27 +++++++++++++++-----
 src/core/security/security_connector.c       | 20 ++++++++++-----
 test/core/security/security_connector_test.c | 23 +++++++++++++++--
 3 files changed, 55 insertions(+), 15 deletions(-)

diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h
index ff16e92c35..e280bf5391 100644
--- a/include/grpc/grpc_security.h
+++ b/include/grpc/grpc_security.h
@@ -143,14 +143,29 @@ grpc_channel_credentials *grpc_google_default_credentials_create(void);
 #define GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR \
   "GRPC_DEFAULT_SSL_ROOTS_FILE_PATH"
 
-/* Overrides the default TLS/SSL roots.
-   The roots must be encoded as PEM and NULL-terminated.
+/* 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.
-   It also does not do any checks about the validity of the encoding.
-   If the GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment is set to a valid path,
-   it will override the roots specified in this function. */
-void grpc_override_ssl_default_roots(const char *roots_pem);
+   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 {
diff --git a/src/core/security/security_connector.c b/src/core/security/security_connector.c
index 8a67243a18..654866fd4e 100644
--- a/src/core/security/security_connector.c
+++ b/src/core/security/security_connector.c
@@ -63,10 +63,10 @@ static const char *installed_roots_path =
 
 /* -- Overridden default roots. -- */
 
-static gpr_slice overridden_default_roots;
+static grpc_ssl_roots_override_callback ssl_roots_override_cb = NULL;
 
-void grpc_override_ssl_default_roots(const char *roots_pem) {
-  overridden_default_roots = gpr_slice_from_copied_string(roots_pem);
+void grpc_set_ssl_roots_override_callback(grpc_ssl_roots_override_callback cb) {
+  ssl_roots_override_cb = cb;
 }
 
 /* -- Cipher suites. -- */
@@ -615,13 +615,19 @@ static gpr_slice compute_default_pem_root_certs_once(void) {
   }
 
   /* Try overridden roots path if needed. */
-  if (GPR_SLICE_IS_EMPTY(result) &&
-      !GPR_SLICE_IS_EMPTY(overridden_default_roots)) {
-    result = gpr_slice_ref(overridden_default_roots);
+  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(result)) {
+  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;
diff --git a/test/core/security/security_connector_test.c b/test/core/security/security_connector_test.c
index bfebf209df..d9322f0257 100644
--- a/test/core/security/security_connector_test.c
+++ b/test/core/security/security_connector_test.c
@@ -47,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) {
@@ -300,8 +301,20 @@ static void test_cn_and_multiple_sans_and_others_ssl_peer_to_auth_context(
   GRPC_AUTH_CONTEXT_UNREF(ctx, "test");
 }
 
+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_override_api = "roots for override api";
   const char *roots_for_env_var = "roots for env var";
 
   char *roots_env_var_file_path;
@@ -311,7 +324,7 @@ static void test_default_ssl_roots(void) {
   fclose(roots_env_var_file);
 
   /* First let's get the root through the override (no env are set). */
-  grpc_override_ssl_default_roots(roots_for_override_api);
+  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);
@@ -336,6 +349,12 @@ static void test_default_ssl_roots(void) {
   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);
-- 
GitLab