<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>