<p>Benjamin Keith Ford has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/14220">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">res_stir_shaken: Implemented signature verification.<br><br>There are a lot of moving parts in this patch, but the focus of it is on<br>the verification of the signature using a public key located at the<br>public key URL provided in the JSON payload. First, we check the<br>database to see if we have already downloaded the key. If so, check to<br>see if it has expired. If it has, redownload from the URL. If we don't<br>have an entry in the database, just go ahead and download the public<br>key. The expiration is tested each time we download the file. After<br>that, read the public key from the file and use it to verify the<br>signature. All sanity checking is done when the payload is first<br>received, so the verification is complete once this point is reached.<br><br>Change-Id: I3ba4c63880493bf8c7d17a9cfca1af0e934d1a1c<br>---<br>M include/asterisk/res_stir_shaken.h<br>M res/res_stir_shaken.c<br>M res/res_stir_shaken/certificate.c<br>M res/res_stir_shaken/stir_shaken.c<br>M res/res_stir_shaken/stir_shaken.h<br>5 files changed, 579 insertions(+), 14 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/20/14220/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h</span><br><span>index 16f0139..a65a887 100644</span><br><span>--- a/include/asterisk/res_stir_shaken.h</span><br><span>+++ b/include/asterisk/res_stir_shaken.h</span><br><span>@@ -26,6 +26,21 @@</span><br><span> struct ast_json;</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Verify a JSON STIR/SHAKEN payload</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param header The payload header</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param payload The payload section</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param signature The payload signature</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param algorithm The signature algorithm</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param public_key_url The public key URL</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval ast_stir_shaken_payload on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval NULL on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const char *payload, const char *signature,</span><br><span style="color: hsl(120, 100%, 40%);">+     const char *algorithm, const char *public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span>  * \brief Retrieve the stir/shaken sorcery context</span><br><span>  *</span><br><span>  * \retval The stir/shaken sorcery context</span><br><span>@@ -41,6 +56,11 @@</span><br><span>  * \brief Sign a JSON STIR/SHAKEN payload</span><br><span>  *</span><br><span>  * \note This function will automatically add the "attest", "iat", and "origid" fields.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json The JWT to sign</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval ast_stir_shaken_payload on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval NULL on failure</span><br><span>  */</span><br><span> struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json);</span><br><span> </span><br><span>diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c</span><br><span>index 3ea7ae9..eaf8d80 100644</span><br><span>--- a/res/res_stir_shaken.c</span><br><span>+++ b/res/res_stir_shaken.c</span><br><span>@@ -18,15 +18,21 @@</span><br><span> </span><br><span> /*** MODULEINFO</span><br><span>    <depend>crypto</depend></span><br><span style="color: hsl(120, 100%, 40%);">+   <depend>curl</depend></span><br><span style="color: hsl(120, 100%, 40%);">+     <depend>res_curl</depend></span><br><span>        <support_level>core</support_level></span><br><span>  ***/</span><br><span> </span><br><span> #include "asterisk.h"</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+#include <curl/curl.h></span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> #include "asterisk/module.h"</span><br><span> #include "asterisk/sorcery.h"</span><br><span> #include "asterisk/time.h"</span><br><span> #include "asterisk/json.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/astdb.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/paths.h"</span><br><span> </span><br><span> #include "asterisk/res_stir_shaken.h"</span><br><span> #include "res_stir_shaken/stir_shaken.h"</span><br><span>@@ -40,6 +46,21 @@</span><br><span> </span><br><span> static struct ast_sorcery *stir_shaken_sorcery;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/* Used for AstDB entries */</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_DB_FAMILY "STIR_SHAKEN"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Used to check CURL headers */</span><br><span style="color: hsl(120, 100%, 40%);">+#define  MAX_HEADER_LENGTH 1023</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* The amount of time (in seconds) to wait before timing out a CURL request */</span><br><span style="color: hsl(120, 100%, 40%);">+#define CURL_TIMEOUT_SEC 7</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* The directory name to store keys in. Appended to ast_config_DATA_DIR */</span><br><span style="color: hsl(120, 100%, 40%);">+#define STIR_SHAKEN_DIR_NAME "stir_shaken_keys"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Used for CURL requests */</span><br><span style="color: hsl(120, 100%, 40%);">+#define GLOBAL_USERAGENT "asterisk-libcurl-agent/1.0"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> struct ast_stir_shaken_payload {</span><br><span>  /*! The JWT header */</span><br><span>        struct ast_json *header;</span><br><span>@@ -74,6 +95,523 @@</span><br><span> }</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Called when a CURL request completes</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param data The hash used to store data in AstDB for the public key</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static size_t curl_header_callback(char *buffer, size_t size, size_t nitems, void *data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+  size_t realsize;</span><br><span style="color: hsl(120, 100%, 40%);">+      char *hash = data;</span><br><span style="color: hsl(120, 100%, 40%);">+    char *header;</span><br><span style="color: hsl(120, 100%, 40%);">+ char *value;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        realsize = size * nitems;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (realsize > MAX_HEADER_LENGTH) {</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_log(LOG_WARNING, "CURL header length is too large (size: '%zu' | max: '%d')\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                 realsize, MAX_HEADER_LENGTH);</span><br><span style="color: hsl(120, 100%, 40%);">+         return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   header = ast_alloca(realsize + 1);</span><br><span style="color: hsl(120, 100%, 40%);">+    memcpy(header, buffer, realsize);</span><br><span style="color: hsl(120, 100%, 40%);">+     header[realsize] = '\0';</span><br><span style="color: hsl(120, 100%, 40%);">+      value = strchr(header, ':');</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!value) {</span><br><span style="color: hsl(120, 100%, 40%);">+         return realsize;</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+     *value++ = '\0';</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    if (strcasecmp(header, "Cache-Control") && strcasecmp(header, "Expires")) {</span><br><span style="color: hsl(120, 100%, 40%);">+               return realsize;</span><br><span style="color: hsl(120, 100%, 40%);">+      }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   value = ast_trim_blanks(ast_skip_blanks(value));</span><br><span style="color: hsl(120, 100%, 40%);">+      header = ast_str_to_lower(header);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  ast_db_put(hash, header, value);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    return realsize;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Sets the expiration for the public key based on the provided fields.</span><br><span style="color: hsl(120, 100%, 40%);">+ * If Cache-Control is present, use it. Otherwise, use Expires.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param hash The hash for the public key URL</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static void set_public_key_expiration(char *hash)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+    char time_buf[32];</span><br><span style="color: hsl(120, 100%, 40%);">+    char value[128] = "";</span><br><span style="color: hsl(120, 100%, 40%);">+       struct timeval actual_expires = ast_tvnow();</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_db_get(hash, "cache-control", value, sizeof(value));</span><br><span style="color: hsl(120, 100%, 40%);">+    if (ast_strlen_zero(value)) {</span><br><span style="color: hsl(120, 100%, 40%);">+         char *str_max_age;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          str_max_age = strstr(value, "s-maxage");</span><br><span style="color: hsl(120, 100%, 40%);">+            if (!str_max_age) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   str_max_age = strstr(value, "max-age");</span><br><span style="color: hsl(120, 100%, 40%);">+             }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           if (str_max_age) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    unsigned int max_age;</span><br><span style="color: hsl(120, 100%, 40%);">+                 char *equal = strchr(str_max_age, '=');</span><br><span style="color: hsl(120, 100%, 40%);">+                       if (equal && (sscanf(equal + 1, "%30u", &max_age) == 1)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                              actual_expires.tv_sec += max_age;</span><br><span style="color: hsl(120, 100%, 40%);">+                     }</span><br><span style="color: hsl(120, 100%, 40%);">+             }</span><br><span style="color: hsl(120, 100%, 40%);">+     } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_db_get(hash, "expires", value, sizeof(value));</span><br><span style="color: hsl(120, 100%, 40%);">+          if (ast_strlen_zero(value)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                 struct tm expires_time;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                     strptime(value, "%a, %d %b %Y %T %z", &expires_time);</span><br><span style="color: hsl(120, 100%, 40%);">+                   expires_time.tm_isdst = -1;</span><br><span style="color: hsl(120, 100%, 40%);">+                   actual_expires.tv_sec = mktime(&expires_time);</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   snprintf(time_buf, sizeof(time_buf), "%30lu", actual_expires.tv_sec);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     ast_db_put(hash, "__actual_expires", time_buf);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Check to see if the public key is expired</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param public_key_url The public key URL</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 1 if expired</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 if not expired</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static int public_key_is_expired(const char *public_key_url)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     struct timeval current_time = ast_tvnow();</span><br><span style="color: hsl(120, 100%, 40%);">+    struct timeval expires = { .tv_sec = 0, .tv_usec = 0 };</span><br><span style="color: hsl(120, 100%, 40%);">+       char actual_expires[32];</span><br><span style="color: hsl(120, 100%, 40%);">+      char hash[41];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      ast_sha1_hash(hash, public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+  ast_db_get(hash, "__actual_expires", actual_expires, sizeof(actual_expires));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     if (ast_strlen_zero(actual_expires)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                return 1;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (sscanf(actual_expires, "%lu", &expires.tv_sec) != 1) {</span><br><span style="color: hsl(120, 100%, 40%);">+              return 1;</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return ast_tvcmp(current_time, expires) == -1 ? 0 : 1;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Prepare a CURL instance to use</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param hash The public key URL hash</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval NULL on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval CURL instance on success</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static CURL *get_curl_instance(char *hash)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+       CURL *curl;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ curl = curl_easy_init();</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!curl) {</span><br><span style="color: hsl(120, 100%, 40%);">+          return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);</span><br><span style="color: hsl(120, 100%, 40%);">+  curl_easy_setopt(curl, CURLOPT_TIMEOUT, CURL_TIMEOUT_SEC);</span><br><span style="color: hsl(120, 100%, 40%);">+    curl_easy_setopt(curl, CURLOPT_USERAGENT, GLOBAL_USERAGENT);</span><br><span style="color: hsl(120, 100%, 40%);">+  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);</span><br><span style="color: hsl(120, 100%, 40%);">+    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);</span><br><span style="color: hsl(120, 100%, 40%);">+ curl_easy_setopt(curl, CURLOPT_HEADERDATA, hash);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return curl;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief CURL the public key from the provided URL to the specified path</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param public_key_URL The public key URL</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param path The path to download the file to</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 1 on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 on success</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static int curl_public_key(const char *public_key_url, const char *path)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   FILE *public_key_file;</span><br><span style="color: hsl(120, 100%, 40%);">+        long http_code;</span><br><span style="color: hsl(120, 100%, 40%);">+       CURL *curl;</span><br><span style="color: hsl(120, 100%, 40%);">+   char curl_errbuf[CURL_ERROR_SIZE + 1];</span><br><span style="color: hsl(120, 100%, 40%);">+        char hash[41];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      ast_sha1_hash(hash, public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        curl_errbuf[CURL_ERROR_SIZE] = '\0';</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        public_key_file = fopen(path, "wb");</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!public_key_file) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_log(LOG_ERROR, "Failed to open file '%s' to write public key from '%s': %s (%d)\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                     path, public_key_url, strerror(errno), errno);</span><br><span style="color: hsl(120, 100%, 40%);">+                return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   curl = get_curl_instance(hash);</span><br><span style="color: hsl(120, 100%, 40%);">+       if (!curl) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "Failed to set up CURL instance for '%s'\n", public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+            fclose(public_key_file);</span><br><span style="color: hsl(120, 100%, 40%);">+              return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   curl_easy_setopt(curl, CURLOPT_URL, public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+  curl_easy_setopt(curl, CURLOPT_WRITEDATA, public_key_file);</span><br><span style="color: hsl(120, 100%, 40%);">+   curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (curl_easy_perform(curl)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_log(LOG_ERROR, "%s\n", curl_errbuf);</span><br><span style="color: hsl(120, 100%, 40%);">+            fclose(public_key_file);</span><br><span style="color: hsl(120, 100%, 40%);">+              return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    curl_easy_cleanup(curl);</span><br><span style="color: hsl(120, 100%, 40%);">+      fclose(public_key_file);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    if (http_code / 100 != 2) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "Failed to retrieve URL '%s': code %ld\n", public_key_url, http_code);</span><br><span style="color: hsl(120, 100%, 40%);">+           return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   set_public_key_expiration(hash);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Get the file name from the provided URL</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param url The URL</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static char *get_filename_from_url(const char *url)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+        size_t size;</span><br><span style="color: hsl(120, 100%, 40%);">+  char *res, *buf;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    buf = ast_strdup(url);</span><br><span style="color: hsl(120, 100%, 40%);">+        size = strlen(buf);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ while (size && buf[size - 1] == '/') {</span><br><span style="color: hsl(120, 100%, 40%);">+                size--;</span><br><span style="color: hsl(120, 100%, 40%);">+               buf[size] = '\0';</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   res = strrchr(buf, '/');</span><br><span style="color: hsl(120, 100%, 40%);">+      if (res && res[1] != '\0') {</span><br><span style="color: hsl(120, 100%, 40%);">+          res = (res + 1);</span><br><span style="color: hsl(120, 100%, 40%);">+      } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              res = buf;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return res;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Returns the path to the downloaded file for the provided URL</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param public_key_url The public key URL</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval Empty string if not present in AstDB</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval The file path if present in AstDB</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static char *get_path_to_public_key(const char *public_key_url)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+    char hash[41];</span><br><span style="color: hsl(120, 100%, 40%);">+        char file_path[256];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_sha1_hash(hash, public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_db_get(hash, "path", file_path, sizeof(file_path));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_strlen_zero(file_path)) {</span><br><span style="color: hsl(120, 100%, 40%);">+             file_path[0] = '\0';</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return ast_strdup(file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Add the public key and its file path to AstDB</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param public_key_url The public key URL</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param filepath The path to the file</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static void add_public_key_to_astdb(const char *public_key_url, const char *filepath)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     char hash[41];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      ast_sha1_hash(hash, public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_db_put(AST_DB_FAMILY, public_key_url, hash);</span><br><span style="color: hsl(120, 100%, 40%);">+      ast_db_put(hash, "path", filepath);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Remove the public key and associated information from AstDB</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param public_key_url The public key URL</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static void remove_public_key_from_astdb(const char *public_key_url)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ char hash[41];</span><br><span style="color: hsl(120, 100%, 40%);">+        char filepath[256];</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_sha1_hash(hash, public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        /* Remove this public key from storage */</span><br><span style="color: hsl(120, 100%, 40%);">+     ast_db_get(hash, "path", filepath, sizeof(filepath));</span><br><span style="color: hsl(120, 100%, 40%);">+       remove(filepath);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_db_del(AST_DB_FAMILY, public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_db_deltree(hash, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Verifies the signature using a public key</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param msg The payload</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param signature The signature to verify</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param public_key The public key used for verification</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval -1 on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 on success</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static int stir_shaken_verify_signature(const char *msg, const char *signature, EVP_PKEY *public_key)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+       EVP_MD_CTX *mdctx = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+     int ret = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+  unsigned char *decoded_signature;</span><br><span style="color: hsl(120, 100%, 40%);">+     size_t signature_length, decoded_signature_length, padding = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     mdctx = EVP_MD_CTX_create();</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!mdctx) {</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_log(LOG_ERROR, "Failed to create Message Digest Context\n");</span><br><span style="color: hsl(120, 100%, 40%);">+            return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ret = EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, public_key);</span><br><span style="color: hsl(120, 100%, 40%);">+      if (ret != 1) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_log(LOG_ERROR, "Failed to initialize Message Digest Context\n");</span><br><span style="color: hsl(120, 100%, 40%);">+                EVP_MD_CTX_destroy(mdctx);</span><br><span style="color: hsl(120, 100%, 40%);">+            return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ret = EVP_DigestVerifyUpdate(mdctx, (unsigned char *)msg, strlen(msg));</span><br><span style="color: hsl(120, 100%, 40%);">+       if (ret != 1) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_log(LOG_ERROR, "Failed to update Message Digest Context\n");</span><br><span style="color: hsl(120, 100%, 40%);">+            EVP_MD_CTX_destroy(mdctx);</span><br><span style="color: hsl(120, 100%, 40%);">+            return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* We need to decode the signature from base64 to bytes */</span><br><span style="color: hsl(120, 100%, 40%);">+    signature_length = strlen(signature);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       if (signature[signature_length - 1] == '=' && signature[signature_length - 2] == '=') {</span><br><span style="color: hsl(120, 100%, 40%);">+               padding = 2;</span><br><span style="color: hsl(120, 100%, 40%);">+  } else if (signature[signature_length - 1] == '=') {</span><br><span style="color: hsl(120, 100%, 40%);">+          padding = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   decoded_signature_length = signature_length * 3 / 4 - padding;</span><br><span style="color: hsl(120, 100%, 40%);">+        decoded_signature = ast_calloc(1, decoded_signature_length);</span><br><span style="color: hsl(120, 100%, 40%);">+  ast_base64decode(decoded_signature, signature, decoded_signature_length);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ret = EVP_DigestVerifyFinal(mdctx, decoded_signature, strlen((const char *)decoded_signature));</span><br><span style="color: hsl(120, 100%, 40%);">+       if (ret != 1) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_log(LOG_ERROR, "Failed final phase of signature verification\n");</span><br><span style="color: hsl(120, 100%, 40%);">+               EVP_MD_CTX_destroy(mdctx);</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_free(decoded_signature);</span><br><span style="color: hsl(120, 100%, 40%);">+          return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   EVP_MD_CTX_destroy(mdctx);</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_free(decoded_signature);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const char *payload, const char *signature,</span><br><span style="color: hsl(120, 100%, 40%);">+        const char *algorithm, const char *public_key_url)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_stir_shaken_payload *ret_payload;</span><br><span style="color: hsl(120, 100%, 40%);">+  EVP_PKEY *public_key;</span><br><span style="color: hsl(120, 100%, 40%);">+ char file_path[256], default_path[256], stir_shaken_dir[256];</span><br><span style="color: hsl(120, 100%, 40%);">+ char *filename;</span><br><span style="color: hsl(120, 100%, 40%);">+       struct ast_json *json_header, *json_payload;</span><br><span style="color: hsl(120, 100%, 40%);">+  int curl = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       if (ast_strlen_zero(header)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_log(LOG_ERROR, "'header' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_strlen_zero(payload)) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_log(LOG_ERROR, "'payload' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(120, 100%, 40%);">+         return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_strlen_zero(signature)) {</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_log(LOG_ERROR, "'signature' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_strlen_zero(algorithm)) {</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_log(LOG_ERROR, "'algorithm' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(120, 100%, 40%);">+               return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_strlen_zero(public_key_url)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_log(LOG_ERROR, "'public_key_url' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Set up the default path and create directory if needed */</span><br><span style="color: hsl(120, 100%, 40%);">+  snprintf(stir_shaken_dir, sizeof(stir_shaken_dir), "%s/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_mkdir(stir_shaken_dir, 0777);</span><br><span style="color: hsl(120, 100%, 40%);">+     filename = get_filename_from_url(public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+     snprintf(default_path, sizeof(default_path), "%s/%s", stir_shaken_dir, filename);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Check to see if we have already downloaded this public key */</span><br><span style="color: hsl(120, 100%, 40%);">+      snprintf(file_path, sizeof(file_path), "%s", get_path_to_public_key(public_key_url));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* If we don't have an entry in AstDB, CURL from the provided URL */</span><br><span style="color: hsl(120, 100%, 40%);">+      if (ast_strlen_zero(file_path)) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           /* Remove this entry from the database, since we will be</span><br><span style="color: hsl(120, 100%, 40%);">+               * downloading a new file anyways.</span><br><span style="color: hsl(120, 100%, 40%);">+             */</span><br><span style="color: hsl(120, 100%, 40%);">+           remove_public_key_from_astdb(public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+               if (curl_public_key(public_key_url, default_path)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                  ast_log(LOG_ERROR, "Could not retrieve public key for '%s'\n", public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+                     return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+          }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           /* Signal that we have already downloaded a new file, no reason to do it again */</span><br><span style="color: hsl(120, 100%, 40%);">+             curl = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           snprintf(file_path, sizeof(file_path), "%s", default_path);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+               /* We should have a successful download at this point, so</span><br><span style="color: hsl(120, 100%, 40%);">+              * add an entry to the database.</span><br><span style="color: hsl(120, 100%, 40%);">+               */</span><br><span style="color: hsl(120, 100%, 40%);">+           add_public_key_to_astdb(public_key_url, file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* Check to see if the key we downloaded (or already had) is expired */</span><br><span style="color: hsl(120, 100%, 40%);">+       if (public_key_is_expired(public_key_url)) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_debug(3, "Public key '%s' is expired\n", public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+             remove_public_key_from_astdb(public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+               /* We downloaded a new file, so if it's expired, there's nothing we can do */</span><br><span style="color: hsl(120, 100%, 40%);">+         if (curl) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   ast_log(LOG_ERROR, "Newly downloaded public key '%s' is expired\n", file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+                     return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+          }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           if (curl_public_key(public_key_url, default_path)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                  ast_log(LOG_ERROR, "Could not retrieve public key for '%s'\n", public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+                     return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+          }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           if (public_key_is_expired(public_key_url)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                  ast_log(LOG_ERROR, "Newly downloaded public key '%s' is expired\n", file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+                     return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+          }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           curl = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           add_public_key_to_astdb(public_key_url, file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* First attempt to read the key. If it fails, try downloading the file,</span><br><span style="color: hsl(120, 100%, 40%);">+       * unless we already did. Check for expiration again */</span><br><span style="color: hsl(120, 100%, 40%);">+       public_key = read_key(file_path, 0);</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!public_key) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_debug(3, "Failed first read of public key file '%s'\n", file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           remove_public_key_from_astdb(public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+               /* We downloaded a new file and still couldn't read the public key */</span><br><span style="color: hsl(120, 100%, 40%);">+             if (curl) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   ast_log(LOG_ERROR, "Failed to read public key from newly downloaded file '%s'\n", file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+                       return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+          }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           if (curl_public_key(public_key_url, file_path)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                     ast_log(LOG_ERROR, "Could not retrieve public key for '%s'\n", public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+                     return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+          }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           if (public_key_is_expired(public_key_url)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                  ast_log(LOG_ERROR, "Newly downloaded public key '%s' is expired\n", file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+                     return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+          }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           public_key = read_key(file_path, 0);</span><br><span style="color: hsl(120, 100%, 40%);">+          if (!public_key) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    ast_log(LOG_ERROR, "Failed to read public key from '%s'\n", file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+                     return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+          }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           add_public_key_to_astdb(public_key_url, file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (stir_shaken_verify_signature(payload, signature, public_key)) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "Failed to verify signature\n");</span><br><span style="color: hsl(120, 100%, 40%);">+         return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ret_payload = ast_calloc(1, sizeof(*ret_payload));</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!ret_payload) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "Failed to allocate STIR/SHAKEN payload\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   json_header = ast_json_pack(header);</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!json_header) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "Failed to create JSON from header\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_stir_shaken_payload_free(ret_payload);</span><br><span style="color: hsl(120, 100%, 40%);">+            return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+     ret_payload->header = json_header;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       json_payload = ast_json_pack(payload);</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!json_payload) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "Failed to create JSON from payload\n");</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_stir_shaken_payload_free(ret_payload);</span><br><span style="color: hsl(120, 100%, 40%);">+            return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+     ret_payload->payload = json_payload;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     ret_payload->signature = (unsigned char *)ast_strdupa(signature);</span><br><span style="color: hsl(120, 100%, 40%);">+  ret_payload->algorithm = ast_strdupa(algorithm);</span><br><span style="color: hsl(120, 100%, 40%);">+   ret_payload->public_key_url = ast_strdupa(public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       return ret_payload;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span>  * \brief Verifies the necessary contents are in the JSON and returns a</span><br><span>  * ast_stir_shaken_payload with the extracted values.</span><br><span>  *</span><br><span>@@ -90,7 +628,7 @@</span><br><span> </span><br><span>     payload = ast_calloc(1, sizeof(*payload));</span><br><span>   if (!payload) {</span><br><span style="color: hsl(0, 100%, 40%);">-         ast_log(LOG_ERROR, "Failed to allocate STIR_SHAKEN payload\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_log(LOG_ERROR, "Failed to allocate STIR/SHAKEN payload\n");</span><br><span>            goto cleanup;</span><br><span>        }</span><br><span> </span><br><span>@@ -234,7 +772,7 @@</span><br><span>  /* There are 6 bits to 1 base64 digit, so in order to get the size of the base64 encoded</span><br><span>      * signature, we need to multiply by the number of bits in a byte and divide by 6. Since</span><br><span>      * there's rounding when doing base64 conversions, add 3 bytes, just in case, and account</span><br><span style="color: hsl(0, 100%, 40%);">-    * for padding. Add another byte for the NULL-terminator so we don't lose data.</span><br><span style="color: hsl(120, 100%, 40%);">+    * for padding. Add another byte for the NULL-terminator.</span><br><span>     */</span><br><span>  encoded_length = ((signature_length * 4 / 3 + 3) & ~3) + 1;</span><br><span>      encoded_signature = ast_calloc(1, encoded_length);</span><br><span>diff --git a/res/res_stir_shaken/certificate.c b/res/res_stir_shaken/certificate.c</span><br><span>index 812fc1e..cc4a144 100644</span><br><span>--- a/res/res_stir_shaken/certificate.c</span><br><span>+++ b/res/res_stir_shaken/certificate.c</span><br><span>@@ -119,7 +119,7 @@</span><br><span>            return -1;</span><br><span>   }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   private_key = read_private_key(cert->path);</span><br><span style="color: hsl(120, 100%, 40%);">+        private_key = read_key(cert->path, 1);</span><br><span>    if (!private_key) {</span><br><span>          return -1;</span><br><span>   }</span><br><span>diff --git a/res/res_stir_shaken/stir_shaken.c b/res/res_stir_shaken/stir_shaken.c</span><br><span>index 5f5c054..bb6cc18 100644</span><br><span>--- a/res/res_stir_shaken/stir_shaken.c</span><br><span>+++ b/res/res_stir_shaken/stir_shaken.c</span><br><span>@@ -83,9 +83,9 @@</span><br><span>       return NULL;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-EVP_PKEY *read_private_key(const char *path)</span><br><span style="color: hsl(120, 100%, 40%);">+EVP_PKEY *read_key(const char *path, int priv)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-  EVP_PKEY *private_key = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ EVP_PKEY *key = NULL;</span><br><span>        FILE *fp;</span><br><span> </span><br><span>        fp = fopen(path, "r");</span><br><span>@@ -94,20 +94,26 @@</span><br><span>               return NULL;</span><br><span>         }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   if (!PEM_read_PrivateKey(fp, &private_key, NULL, NULL)) {</span><br><span style="color: hsl(0, 100%, 40%);">-           ast_log(LOG_ERROR, "Failed to read private key from file '%s'\n", path);</span><br><span style="color: hsl(120, 100%, 40%);">+    if (priv) {</span><br><span style="color: hsl(120, 100%, 40%);">+           key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+      } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              key = PEM_read_PUBKEY(fp, NULL, NULL, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+  }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (!key) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "Failed to read %s key from file '%s'\n", priv ? "private" : "public", path);</span><br><span>               fclose(fp);</span><br><span>          return NULL;</span><br><span>         }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   if (EVP_PKEY_id(private_key) != EVP_PKEY_EC) {</span><br><span style="color: hsl(0, 100%, 40%);">-          ast_log(LOG_ERROR, "Private key from '%s' must be of type EVP_PKEY_EC\n", path);</span><br><span style="color: hsl(120, 100%, 40%);">+    if (EVP_PKEY_id(key) != EVP_PKEY_EC) {</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_log(LOG_ERROR, "%s key from '%s' must be of type EVP_PKEY_EC\n", priv ? "private" : "public", path);</span><br><span>               fclose(fp);</span><br><span style="color: hsl(0, 100%, 40%);">-             EVP_PKEY_free(private_key);</span><br><span style="color: hsl(120, 100%, 40%);">+           EVP_PKEY_free(key);</span><br><span>          return NULL;</span><br><span>         }</span><br><span> </span><br><span>        fclose(fp);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- return private_key;</span><br><span style="color: hsl(120, 100%, 40%);">+   return key;</span><br><span> }</span><br><span>diff --git a/res/res_stir_shaken/stir_shaken.h b/res/res_stir_shaken/stir_shaken.h</span><br><span>index 933b3bb..4e6d305 100644</span><br><span>--- a/res/res_stir_shaken/stir_shaken.h</span><br><span>+++ b/res/res_stir_shaken/stir_shaken.h</span><br><span>@@ -42,13 +42,14 @@</span><br><span> char *stir_shaken_tab_complete_name(const char *word, struct ao2_container *container);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(0, 100%, 40%);">- * \brief Reads the private key from the specified path</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Reads the public (or private) key from the specified path</span><br><span>  *</span><br><span>  * \param path The path to the file containing the private key</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param priv Specify 0 for public, 1 for private</span><br><span>  *</span><br><span>  * \retval NULL on failure</span><br><span style="color: hsl(0, 100%, 40%);">- * \retval The private key on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval The public/private key on success</span><br><span>  */</span><br><span style="color: hsl(0, 100%, 40%);">-EVP_PKEY *read_private_key(const char *path);</span><br><span style="color: hsl(120, 100%, 40%);">+EVP_PKEY *read_key(const char *path, int priv);</span><br><span> </span><br><span> #endif /* _STIR_SHAKEN_H */</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/14220">change 14220</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/c/asterisk/+/14220"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I3ba4c63880493bf8c7d17a9cfca1af0e934d1a1c </div>
<div style="display:none"> Gerrit-Change-Number: 14220 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Benjamin Keith Ford <bford@digium.com> </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>