<p>Joshua Colp <strong>submitted</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/14220">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Joshua Colp: Looks good to me, approved; Approved for Submit
  Kevin Harwell: Looks good to me, but someone else must approve

</div><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>The XML has also been added since a new config option was added to<br>general (curl_timeout). The maximum amount of time to wait for a<br>download can be configured through this option, with a low value by<br>default.<br><br>Change-Id: I3ba4c63880493bf8c7d17a9cfca1af0e934d1a1c<br>---<br>M Makefile<br>A doc/UPGRADE-staging/res_stir_shaken_directory.txt<br>M include/asterisk/res_stir_shaken.h<br>M res/res_stir_shaken.c<br>M res/res_stir_shaken/certificate.c<br>A res/res_stir_shaken/curl.c<br>A res/res_stir_shaken/curl.h<br>M res/res_stir_shaken/general.c<br>M res/res_stir_shaken/general.h<br>M res/res_stir_shaken/stir_shaken.c<br>M res/res_stir_shaken/stir_shaken.h<br>11 files changed, 832 insertions(+), 18 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/Makefile b/Makefile</span><br><span>index d2b17ff..3d11ccd 100644</span><br><span>--- a/Makefile</span><br><span>+++ b/Makefile</span><br><span>@@ -565,7 +565,7 @@</span><br><span>         "$(ASTDATADIR)/firmware/iax" "$(ASTDATADIR)/images" "$(ASTDATADIR)/keys" \</span><br><span>     "$(ASTDATADIR)/phoneprov" "$(ASTDATADIR)/rest-api" "$(ASTDATADIR)/static-http" \</span><br><span>       "$(ASTDATADIR)/sounds" "$(ASTDATADIR)/moh" "$(ASTMANDIR)/man8" "$(AGI_DIR)" "$(ASTDBDIR)" \</span><br><span style="color: hsl(0, 100%, 40%);">-   "$(ASTDATADIR)/third-party"</span><br><span style="color: hsl(120, 100%, 40%);">+ "$(ASTDATADIR)/third-party" "${ASTDATADIR}/keys/stir_shaken"</span><br><span> </span><br><span> installdirs:</span><br><span>         @for i in $(INSTALLDIRS); do \</span><br><span>diff --git a/doc/UPGRADE-staging/res_stir_shaken_directory.txt b/doc/UPGRADE-staging/res_stir_shaken_directory.txt</span><br><span>new file mode 100644</span><br><span>index 0000000..160241e</span><br><span>--- /dev/null</span><br><span>+++ b/doc/UPGRADE-staging/res_stir_shaken_directory.txt</span><br><span>@@ -0,0 +1,5 @@</span><br><span style="color: hsl(120, 100%, 40%);">+Subject: res_stir_shaken</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+A new directory has been added under the default (e.g., /var/lib/asterisk) -</span><br><span style="color: hsl(120, 100%, 40%);">+inside the 'keys' directory - named 'stir_shaken'. This directory will</span><br><span style="color: hsl(120, 100%, 40%);">+hold public keys that have been downloaded for STIR/SHAKEN verification.</span><br><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..3f79596 100644</span><br><span>--- a/res/res_stir_shaken.c</span><br><span>+++ b/res/res_stir_shaken.c</span><br><span>@@ -18,6 +18,8 @@</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>@@ -27,12 +29,73 @@</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 style="color: hsl(120, 100%, 40%);">+#include "asterisk/conversions.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> #include "res_stir_shaken/general.h"</span><br><span> #include "res_stir_shaken/store.h"</span><br><span> #include "res_stir_shaken/certificate.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "res_stir_shaken/curl.h"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*** DOCUMENTATION</span><br><span style="color: hsl(120, 100%, 40%);">+      <configInfo name="res_stir_shaken" language="en_US"></span><br><span style="color: hsl(120, 100%, 40%);">+                <synopsis>STIR/SHAKEN module for Asterisk</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+              <configFile name="stir_shaken.conf"></span><br><span style="color: hsl(120, 100%, 40%);">+                  <configObject name="general"></span><br><span style="color: hsl(120, 100%, 40%);">+                         <synopsis>STIR/SHAKEN general options</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                          <configOption name="type"></span><br><span style="color: hsl(120, 100%, 40%);">+                                    <synopsis>Must be of type 'general'.</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                           </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                         <configOption name="ca_file" default=""></span><br><span style="color: hsl(120, 100%, 40%);">+                                    <synopsis>File path to the certificate authority certificate</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                           </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                         <configOption name="ca_path" default=""></span><br><span style="color: hsl(120, 100%, 40%);">+                                    <synopsis>File path to a chain of trust</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                                </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                         <configOption name="cache_max_size" default="1000"></span><br><span style="color: hsl(120, 100%, 40%);">+                                 <synopsis>Maximum size to use for caching public keys</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                          </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                         <configOption name="curl_timeout" default="2"></span><br><span style="color: hsl(120, 100%, 40%);">+                                      <synopsis>Maximum time to wait to CURL certificates</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                            </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                 </configObject></span><br><span style="color: hsl(120, 100%, 40%);">+                 <configObject name="store"></span><br><span style="color: hsl(120, 100%, 40%);">+                           <synopsis>STIR/SHAKEN certificate store options</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                                <configOption name="type"></span><br><span style="color: hsl(120, 100%, 40%);">+                                    <synopsis>Must be of type 'store'.</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                             </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                         <configOption name="path" default=""></span><br><span style="color: hsl(120, 100%, 40%);">+                                       <synopsis>Path to a directory containing certificates</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                          </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                         <configOption name="public_key_url" default=""></span><br><span style="color: hsl(120, 100%, 40%);">+                                     <synopsis>URL to the public key(s)</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                                     <description><para></span><br><span style="color: hsl(120, 100%, 40%);">+                                        Must be a valid http, or https, URL. The URL must also contain the ${CERTIFICATE} variable, which is used for public key name substitution.</span><br><span style="color: hsl(120, 100%, 40%);">+                                   For example: http://mycompany.com/${CERTIFICATE}.pub</span><br><span style="color: hsl(120, 100%, 40%);">+                                 </para></description></span><br><span style="color: hsl(120, 100%, 40%);">+                             </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                 </configObject></span><br><span style="color: hsl(120, 100%, 40%);">+                 <configObject name="certificate"></span><br><span style="color: hsl(120, 100%, 40%);">+                             <synopsis>STIR/SHAKEN certificate options</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                              <configOption name="type"></span><br><span style="color: hsl(120, 100%, 40%);">+                                    <synopsis>Must be of type 'certificate'.</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                               </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                         <configOption name="path" default=""></span><br><span style="color: hsl(120, 100%, 40%);">+                                       <synopsis>File path to a certificate</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                           </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                         <configOption name="public_key_url" default=""></span><br><span style="color: hsl(120, 100%, 40%);">+                                     <synopsis>URL to the public key</synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+                                        <description><para></span><br><span style="color: hsl(120, 100%, 40%);">+                                        Must be a valid http, or https, URL.</span><br><span style="color: hsl(120, 100%, 40%);">+                                 </para></description></span><br><span style="color: hsl(120, 100%, 40%);">+                             </configOption></span><br><span style="color: hsl(120, 100%, 40%);">+                 </configObject></span><br><span style="color: hsl(120, 100%, 40%);">+         </configFile></span><br><span style="color: hsl(120, 100%, 40%);">+   </configInfo></span><br><span style="color: hsl(120, 100%, 40%);">+ ***/</span><br><span> </span><br><span> #define STIR_SHAKEN_ENCRYPTION_ALGORITHM "ES256"</span><br><span> #define STIR_SHAKEN_PPT "shaken"</span><br><span>@@ -40,6 +103,15 @@</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%);">+/* 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"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* The maximum length for path storage */</span><br><span style="color: hsl(120, 100%, 40%);">+#define MAX_PATH_LEN 256</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 +146,424 @@</span><br><span> }</span><br><span> </span><br><span> /*!</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%);">+ * \param data The CURL callback data containing expiration data</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static void set_public_key_expiration(const char *public_key_url, const struct curl_cb_data *data)</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;</span><br><span style="color: hsl(120, 100%, 40%);">+  struct timeval actual_expires = ast_tvnow();</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%);">+        value = curl_cb_data_get_cache_control(data);</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 && !ast_str_to_uint(equal + 1, &max_age)) {</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%);">+              value = curl_cb_data_get_expires(data);</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, "expiration", 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 expiration[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, "expiration", expiration, sizeof(expiration));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_strlen_zero(expiration)) {</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 (ast_str_to_ulong(expiration, (unsigned long *)&expires.tv_sec)) {</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 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[MAX_PATH_LEN];</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 details and 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 details 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[MAX_PATH_LEN];</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%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /* Remove the actual file from the system */</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. Make sure we have</span><br><span style="color: hsl(120, 100%, 40%);">+     * at least enough characters for this check */</span><br><span style="color: hsl(120, 100%, 40%);">+       signature_length = strlen(signature);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (signature_length > 2 && signature[signature_length - 1] == '=') {</span><br><span style="color: hsl(120, 100%, 40%);">+              padding++;</span><br><span style="color: hsl(120, 100%, 40%);">+            if (signature[signature_length - 2] == '=') {</span><br><span style="color: hsl(120, 100%, 40%);">+                 padding++;</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%);">+   decoded_signature_length = (signature_length / 4 * 3) - 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, decoded_signature_length);</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%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief CURL the file located at public_key_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 run_curl(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%);">+      struct curl_cb_data *data;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  data = curl_cb_data_create();</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!data) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "Failed to create CURL callback data\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%);">+   if (curl_public_key(public_key_url, path, data)) {</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%);">+             curl_cb_data_free(data);</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(public_key_url, data);</span><br><span style="color: hsl(120, 100%, 40%);">+      curl_cb_data_free(data);</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 Downloads the public key from public_key_url. If curl is non-zero, that signals</span><br><span style="color: hsl(120, 100%, 40%);">+ * CURL has already been run, and we should bail here. The entry is added to AstDB as well.</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%);">+ * \param curl Flag signaling if we have run CURL or not</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_and_check_expiration(const char *public_key_url, const char *path, int *curl)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ if (curl) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "Already downloaded public key '%s'\n", path);</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 (run_curl(public_key_url, path)) {</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 (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", path);</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 = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+    add_public_key_to_astdb(public_key_url, path);</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 *filename;</span><br><span style="color: hsl(120, 100%, 40%);">+       int curl = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json_error err;</span><br><span style="color: hsl(120, 100%, 40%);">+    RAII_VAR(char *, file_path, NULL, ast_free);</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%);">+   /* Check to see if we have already downloaded this public key. The reason we</span><br><span style="color: hsl(120, 100%, 40%);">+   * store the file path is because:</span><br><span style="color: hsl(120, 100%, 40%);">+     *</span><br><span style="color: hsl(120, 100%, 40%);">+     * 1. If, for some reason, the default directory changes, we still know where</span><br><span style="color: hsl(120, 100%, 40%);">+  * to look for the files we already have.</span><br><span style="color: hsl(120, 100%, 40%);">+      *</span><br><span style="color: hsl(120, 100%, 40%);">+     * 2. In the future, if we want to add a way to store the keys in multiple</span><br><span style="color: hsl(120, 100%, 40%);">+     * {configurable) directories, we already have the storage mechanism in place.</span><br><span style="color: hsl(120, 100%, 40%);">+         * The only thing that would be left to do is pull from the configuration.</span><br><span style="color: hsl(120, 100%, 40%);">+     */</span><br><span style="color: hsl(120, 100%, 40%);">+   file_path = 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%);">+           size_t file_path_size;</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%);">+               /* Go ahead and free file_path, in case anything was allocated above */</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_free(file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                /* Set up the default path */</span><br><span style="color: hsl(120, 100%, 40%);">+         filename = basename(public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+          file_path_size = strlen(ast_config_AST_DATA_DIR) + 3 + strlen(STIR_SHAKEN_DIR_NAME) + strlen(filename) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+           file_path = ast_calloc(1, file_path_size);</span><br><span style="color: hsl(120, 100%, 40%);">+            snprintf(file_path, sizeof(*file_path), "%s/keys/%s/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME, filename);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                /* Download to the default path */</span><br><span style="color: hsl(120, 100%, 40%);">+            if (run_curl(public_key_url, 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%);">+           /* 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%);">+           /* 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%);">+               /* If this fails, then there's nothing we can do */</span><br><span style="color: hsl(120, 100%, 40%);">+               if (curl_and_check_expiration(public_key_url, file_path, &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%);">+</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 = stir_shaken_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%);">+               if (curl_and_check_expiration(public_key_url, file_path, &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%);">+           public_key = stir_shaken_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%);">+                     remove_public_key_from_astdb(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%);">+</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%);">+         EVP_PKEY_free(public_key);</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%);">+   /* We don't need the public key anymore */</span><br><span style="color: hsl(120, 100%, 40%);">+        EVP_PKEY_free(public_key);</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%);">+   ret_payload->header = ast_json_load_string(header, &err);</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!ret_payload->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%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ret_payload->payload = ast_json_load_string(payload, &err);</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!ret_payload->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%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ret_payload->signature = (unsigned char *)ast_strdup(signature);</span><br><span style="color: hsl(120, 100%, 40%);">+   ret_payload->algorithm = ast_strdup(algorithm);</span><br><span style="color: hsl(120, 100%, 40%);">+    ret_payload->public_key_url = ast_strdup(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 +580,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 +724,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>@@ -464,9 +954,6 @@</span><br><span>     return AST_MODULE_LOAD_SUCCESS;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-#undef AST_BUILDOPT_SUM</span><br><span style="color: hsl(0, 100%, 40%);">-#define AST_BUILDOPT_SUM ""</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span> AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER,</span><br><span>                               "STIR/SHAKEN Module for Asterisk",</span><br><span>         .support_level = AST_MODULE_SUPPORT_CORE,</span><br><span>@@ -474,4 +961,5 @@</span><br><span>      .unload = unload_module,</span><br><span>     .reload = reload_module,</span><br><span>     .load_pri = AST_MODPRI_CHANNEL_DEPEND - 1,</span><br><span style="color: hsl(120, 100%, 40%);">+    .requires = "res_curl",</span><br><span> );</span><br><span>diff --git a/res/res_stir_shaken/certificate.c b/res/res_stir_shaken/certificate.c</span><br><span>index 812fc1e..e889a36 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 = stir_shaken_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/curl.c b/res/res_stir_shaken/curl.c</span><br><span>new file mode 100644</span><br><span>index 0000000..634c2bf</span><br><span>--- /dev/null</span><br><span>+++ b/res/res_stir_shaken/curl.c</span><br><span>@@ -0,0 +1,199 @@</span><br><span style="color: hsl(120, 100%, 40%);">+/*</span><br><span style="color: hsl(120, 100%, 40%);">+ * Asterisk -- An open source telephony toolkit.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * Copyright (C) 2020, Sangoma Technologies Corporation</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * Ben Ford <bford@sangoma.com></span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * See http://www.asterisk.org for more information about</span><br><span style="color: hsl(120, 100%, 40%);">+ * the Asterisk project. Please do not directly contact</span><br><span style="color: hsl(120, 100%, 40%);">+ * any of the maintainers of this project for assistance;</span><br><span style="color: hsl(120, 100%, 40%);">+ * the project provides a web site, mailing lists and IRC</span><br><span style="color: hsl(120, 100%, 40%);">+ * channels for your use.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * This program is free software, distributed under the terms of</span><br><span style="color: hsl(120, 100%, 40%);">+ * the GNU General Public License Version 2. See the LICENSE file</span><br><span style="color: hsl(120, 100%, 40%);">+ * at the top of the source tree.</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%);">+#include "asterisk.h"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/utils.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/logger.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "curl.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "general.h"</span><br><span style="color: hsl(120, 100%, 40%);">+</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 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%);">+/* 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 style="color: hsl(120, 100%, 40%);">+/* CURL callback data to avoid storing useless info in AstDB */</span><br><span style="color: hsl(120, 100%, 40%);">+struct curl_cb_data {</span><br><span style="color: hsl(120, 100%, 40%);">+       char *cache_control;</span><br><span style="color: hsl(120, 100%, 40%);">+  char *expires;</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 curl_cb_data *curl_cb_data_create(void)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+       struct curl_cb_data *data;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  data = ast_calloc(1, sizeof(data));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return data;</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%);">+void curl_cb_data_free(struct curl_cb_data *data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+       if (!data) {</span><br><span style="color: hsl(120, 100%, 40%);">+          return;</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%);">+   ast_free(data->cache_control);</span><br><span style="color: hsl(120, 100%, 40%);">+     ast_free(data->expires);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_free(data);</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%);">+char *curl_cb_data_get_cache_control(const struct curl_cb_data *data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!data) {</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%);">+   return data->cache_control;</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%);">+char *curl_cb_data_get_expires(const struct curl_cb_data *data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+       if (!data) {</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%);">+   return data->expires;</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 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 curl_cb_data structure to store expiration info</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%);">+  struct curl_cb_data *cb_data = data;</span><br><span style="color: hsl(120, 100%, 40%);">+  size_t realsize;</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%);">+      value = ast_trim_blanks(ast_skip_blanks(value));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!strcasecmp(header, "Cache-Control")) {</span><br><span style="color: hsl(120, 100%, 40%);">+         cb_data->cache_control = ast_strdup(value);</span><br><span style="color: hsl(120, 100%, 40%);">+        } else if (!strcasecmp(header, "Expires")) {</span><br><span style="color: hsl(120, 100%, 40%);">+                cb_data->expires = ast_strdup(value);</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 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 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 data The CURL callback data</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(struct curl_cb_data *data)</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%);">+   struct stir_shaken_general *cfg;</span><br><span style="color: hsl(120, 100%, 40%);">+      unsigned int curl_timeout;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  cfg = stir_shaken_general_get();</span><br><span style="color: hsl(120, 100%, 40%);">+      curl_timeout = ast_stir_shaken_curl_timeout(cfg);</span><br><span style="color: hsl(120, 100%, 40%);">+     ao2_cleanup(cfg);</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);</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, data);</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%);">+int curl_public_key(const char *public_key_url, const char *path, struct curl_cb_data *data)</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(data);</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 isntance 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%);">+            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%);">+              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%);">+   return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span>diff --git a/res/res_stir_shaken/curl.h b/res/res_stir_shaken/curl.h</span><br><span>new file mode 100644</span><br><span>index 0000000..d587327</span><br><span>--- /dev/null</span><br><span>+++ b/res/res_stir_shaken/curl.h</span><br><span>@@ -0,0 +1,73 @@</span><br><span style="color: hsl(120, 100%, 40%);">+/*</span><br><span style="color: hsl(120, 100%, 40%);">+ * Asterisk -- An open source telephony toolkit.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * Copyright (C) 2020, Sangoma Technologies Corporation</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * Ben Ford <bford@sangoma.com></span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * See http://www.asterisk.org for more information about</span><br><span style="color: hsl(120, 100%, 40%);">+ * the Asterisk project. Please do not directly contact</span><br><span style="color: hsl(120, 100%, 40%);">+ * any of the maintainers of this project for assistance;</span><br><span style="color: hsl(120, 100%, 40%);">+ * the project provides a web site, mailing lists and IRC</span><br><span style="color: hsl(120, 100%, 40%);">+ * channels for your use.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * This program is free software, distributed under the terms of</span><br><span style="color: hsl(120, 100%, 40%);">+ * the GNU General Public License Version 2. See the LICENSE file</span><br><span style="color: hsl(120, 100%, 40%);">+ * at the top of the source tree.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+#ifndef _STIR_SHAKEN_CURL_H</span><br><span style="color: hsl(120, 100%, 40%);">+#define _STIR_SHAKEN_CURL_H</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Forward declarion for CURL callback data */</span><br><span style="color: hsl(120, 100%, 40%);">+struct curl_cb_data;</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 Allocate memory for a curl_cb_data struct</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note This will need to be freed by the consumer using curl_cb_data_free</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_cb_struct on success</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+struct curl_cb_data *curl_cb_data_create(void);</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 Free a curl_cb_data struct</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param data The curl_cb_data struct to free</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+void curl_cb_data_free(struct curl_cb_data *data);</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 cache_control field from a curl_cb_data struct</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param data The curl_cb_data</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval cache_control on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval NULL otherwise</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+char *curl_cb_data_get_cache_control(const struct curl_cb_data *data);</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 expires field from a curl_cb_data struct</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param data The curl_cb_data</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval expires on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval NULL otherwise</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+char *curl_cb_data_get_expires(const struct curl_cb_data *data);</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%);">+ * \param data The curl_cb_data</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%);">+int curl_public_key(const char *public_key_url, const char *path, struct curl_cb_data *data);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+#endif /* _STIR_SHAKEN_CURL_H */</span><br><span>diff --git a/res/res_stir_shaken/general.c b/res/res_stir_shaken/general.c</span><br><span>index 7e807bb..edf8f85 100644</span><br><span>--- a/res/res_stir_shaken/general.c</span><br><span>+++ b/res/res_stir_shaken/general.c</span><br><span>@@ -30,6 +30,7 @@</span><br><span> #define DEFAULT_CA_FILE ""</span><br><span> #define DEFAULT_CA_PATH ""</span><br><span> #define DEFAULT_CACHE_MAX_SIZE 1000</span><br><span style="color: hsl(120, 100%, 40%);">+#define DEFAULT_CURL_TIMEOUT 2</span><br><span> </span><br><span> struct stir_shaken_general {</span><br><span>        SORCERY_OBJECT(details);</span><br><span>@@ -41,6 +42,8 @@</span><br><span>         );</span><br><span>   /*! Maximum size of public keys cache */</span><br><span>     unsigned int cache_max_size;</span><br><span style="color: hsl(120, 100%, 40%);">+  /*! Maximum time to wait to CURL certificates */</span><br><span style="color: hsl(120, 100%, 40%);">+      unsigned int curl_timeout;</span><br><span> };</span><br><span> </span><br><span> static struct stir_shaken_general *default_config = NULL;</span><br><span>@@ -78,6 +81,11 @@</span><br><span>       return cfg ? cfg->cache_max_size : DEFAULT_CACHE_MAX_SIZE;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+unsigned int ast_stir_shaken_curl_timeout(const struct stir_shaken_general *cfg)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   return cfg ? cfg->curl_timeout : DEFAULT_CURL_TIMEOUT;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static void stir_shaken_general_destructor(void *obj)</span><br><span> {</span><br><span>         struct stir_shaken_general *cfg = obj;</span><br><span>@@ -250,6 +258,9 @@</span><br><span>         ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "cache_max_size",</span><br><span>          __stringify(DEFAULT_CACHE_MAX_SIZE), OPT_UINT_T, 0,</span><br><span>          FLDSET(struct stir_shaken_general, cache_max_size));</span><br><span style="color: hsl(120, 100%, 40%);">+  ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "curl_timeout",</span><br><span style="color: hsl(120, 100%, 40%);">+             __stringify(DEFAULT_CURL_TIMEOUT), OPT_UINT_T, 0,</span><br><span style="color: hsl(120, 100%, 40%);">+             FLDSET(struct stir_shaken_general, curl_timeout));</span><br><span> </span><br><span>       if (ast_sorcery_instance_observer_add(sorcery, &stir_shaken_general_observer)) {</span><br><span>                 ast_log(LOG_ERROR, "stir/shaken - failed to register loaded observer for '%s' "</span><br><span>diff --git a/res/res_stir_shaken/general.h b/res/res_stir_shaken/general.h</span><br><span>index 0c0c5f0..357933b 100644</span><br><span>--- a/res/res_stir_shaken/general.h</span><br><span>+++ b/res/res_stir_shaken/general.h</span><br><span>@@ -73,6 +73,17 @@</span><br><span> unsigned int ast_stir_shaken_cache_max_size(const struct stir_shaken_general *cfg);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Retrieve the 'curl_timeout' general configuration option value</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note If a NULL configuration is given, then the default value is returned</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param cfg A 'general' configuration object</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval The 'curl_timeout' value</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+unsigned int ast_stir_shaken_curl_timeout(const struct stir_shaken_general *cfg);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span>  * \brief Load time initialization for the stir/shaken 'general' configuration</span><br><span>  *</span><br><span>  * \retval 0 on success, -1 on error</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..10caca9 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 *stir_shaken_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..a49050e 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 *stir_shaken_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: 8 </div>
<div style="display:none"> Gerrit-Owner: Benjamin Keith Ford <bford@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@sangoma.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>