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

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">STIR/SHAKEN: Option split and response codes.<br><br>The stir_shaken configuration option now has 4 different choices to pick<br>from: off, attest, verify, and on. Off and on behave the same way they<br>do now. Attest will only perform attestation on the endpoint, and verify<br>will only perform verification on the endpoint.<br><br>Certain responses are required to be sent based on certain conditions<br>for STIR/SHAKEN. For example, if we get a Date header that is outside of<br>the time range that is considered valid, a 403 Stale Date response<br>should be sent. This and several other responses have been added.<br><br>Change-Id: I4ac1ecf652cd0e336006b0ca638dc826b5b1ebf7<br>---<br>A doc/UPGRADE-staging/stir_shaken_option_split.txt<br>M include/asterisk/res_pjsip.h<br>M include/asterisk/res_stir_shaken.h<br>M res/res_pjsip/pjsip_configuration.c<br>M res/res_pjsip_session.c<br>M res/res_pjsip_stir_shaken.c<br>M res/res_stir_shaken.c<br>7 files changed, 420 insertions(+), 114 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/doc/UPGRADE-staging/stir_shaken_option_split.txt b/doc/UPGRADE-staging/stir_shaken_option_split.txt</span><br><span>new file mode 100644</span><br><span>index 0000000..79df214</span><br><span>--- /dev/null</span><br><span>+++ b/doc/UPGRADE-staging/stir_shaken_option_split.txt</span><br><span>@@ -0,0 +1,7 @@</span><br><span style="color: hsl(120, 100%, 40%);">+Subject: STIR/SHAKEN</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+The STIR/SHAKEN configuration option has been split into</span><br><span style="color: hsl(120, 100%, 40%);">+4 different choices: off, attest, verify, and on. Off and</span><br><span style="color: hsl(120, 100%, 40%);">+on behave the same way as before. Attest will only perform</span><br><span style="color: hsl(120, 100%, 40%);">+attestation on the endpoint, and verify will only perform</span><br><span style="color: hsl(120, 100%, 40%);">+verification on the endpoint.</span><br><span>diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h</span><br><span>index 7b9dcbd..2a20e36 100644</span><br><span>--- a/include/asterisk/res_pjsip.h</span><br><span>+++ b/include/asterisk/res_pjsip.h</span><br><span>@@ -63,6 +63,22 @@</span><br><span> #define PJSIP_EXPIRES_NOT_SPECIFIED        ((pj_uint32_t)-1)</span><br><span> #endif</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/* Response codes from RFC8224 */</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_CODE_STALE_DATE 403</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_CODE_USE_IDENTITY_HEADER 428</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_CODE_USE_SUPPORTED_PASSPORT_FORMAT 428</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_CODE_BAD_IDENTITY_INFO 436</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_CODE_UNSUPPORTED_CREDENTIAL 437</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_CODE_INVALID_IDENTITY_HEADER 438</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Response strings from RFC8224 */</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_STR_STALE_DATE "Stale Date"</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_STR_USE_IDENTITY_HEADER "Use Identity Header"</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_STR_USE_SUPPORTED_PASSPORT_FORMAT "Use Supported PASSporT Format"</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_STR_BAD_IDENTITY_INFO "Bad Identity Info"</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_STR_UNSUPPORTED_CREDENTIAL "Unsupported Credential"</span><br><span style="color: hsl(120, 100%, 40%);">+#define AST_STIR_SHAKEN_RESPONSE_STR_INVALID_IDENTITY_HEADER "Invalid Identity Header"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /* Forward declarations of PJSIP stuff */</span><br><span> struct pjsip_rx_data;</span><br><span> struct pjsip_module;</span><br><span>@@ -527,6 +543,17 @@</span><br><span>     AST_SIP_REDIRECT_URI_PJSIP,</span><br><span> };</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+enum ast_sip_stir_shaken_behavior {</span><br><span style="color: hsl(120, 100%, 40%);">+    /*! Don't do any STIR/SHAKEN operations */</span><br><span style="color: hsl(120, 100%, 40%);">+        AST_SIP_STIR_SHAKEN_OFF = 0,</span><br><span style="color: hsl(120, 100%, 40%);">+  /*! Only do STIR/SHAKEN attestation */</span><br><span style="color: hsl(120, 100%, 40%);">+        AST_SIP_STIR_SHAKEN_ATTEST = 1,</span><br><span style="color: hsl(120, 100%, 40%);">+       /*! Only do STIR/SHAKEN verification */</span><br><span style="color: hsl(120, 100%, 40%);">+       AST_SIP_STIR_SHAKEN_VERIFY = 2,</span><br><span style="color: hsl(120, 100%, 40%);">+       /*! Do STIR/SHAKEN attestation and verification */</span><br><span style="color: hsl(120, 100%, 40%);">+    AST_SIP_STIR_SHAKEN_ON = 3,</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*!</span><br><span>  * \brief Incoming/Outgoing call offer/answer joint codec preference.</span><br><span>  *</span><br><span>@@ -917,7 +944,7 @@</span><br><span>        unsigned int suppress_q850_reason_headers;</span><br><span>   /*! Ignore 183 if no SDP is present */</span><br><span>       unsigned int ignore_183_without_sdp;</span><br><span style="color: hsl(0, 100%, 40%);">-    /*! Enable STIR/SHAKEN support on this endpoint */</span><br><span style="color: hsl(120, 100%, 40%);">+    /*! Set which STIR/SHAKEN behaviors we want on this endpoint */</span><br><span>      unsigned int stir_shaken;</span><br><span>    /*! Should we authenticate OPTIONS requests per RFC 3261? */</span><br><span>         unsigned int allow_unauthenticated_options;</span><br><span>diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h</span><br><span>index 5175907..92eb0ec 100644</span><br><span>--- a/include/asterisk/res_stir_shaken.h</span><br><span>+++ b/include/asterisk/res_stir_shaken.h</span><br><span>@@ -29,6 +29,13 @@</span><br><span>        AST_STIR_SHAKEN_VERIFY_PASSED, /*! Signature verified and contents match signaling */</span><br><span> };</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*! Different from ast_stir_shaken_verification_result. Used to determine why ast_stir_shaken_verify returned NULL */</span><br><span style="color: hsl(120, 100%, 40%);">+enum ast_stir_shaken_verify_failure_reason {</span><br><span style="color: hsl(120, 100%, 40%);">+  AST_STIR_SHAKEN_VERIFY_FAILED_MEMORY_ALLOC, /*! Memory allocation failure */</span><br><span style="color: hsl(120, 100%, 40%);">+  AST_STIR_SHAKEN_VERIFY_FAILED_TO_GET_CERT, /*! Failed to get the credentials to verify */</span><br><span style="color: hsl(120, 100%, 40%);">+     AST_STIR_SHAKEN_VERIFY_FAILED_SIGNATURE_VALIDATION, /*! Failed validating the signature */</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> struct ast_stir_shaken_payload;</span><br><span> </span><br><span> struct ast_json;</span><br><span>@@ -88,6 +95,24 @@</span><br><span>     const char *algorithm, const char *public_cert_url);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Same as ast_stir_shaken_verify, but will populate a struct with additional information on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note failure_code will be written to in this function</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_cert_url The public key URL</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param failure_code Additional failure information</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_verify2(const char *header, const char *payload, const char *signature,</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *algorithm, const char *public_cert_url, int *failure_code);</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>diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c</span><br><span>index c27b587..49a7d45 100644</span><br><span>--- a/res/res_pjsip/pjsip_configuration.c</span><br><span>+++ b/res/res_pjsip/pjsip_configuration.c</span><br><span>@@ -717,6 +717,44 @@</span><br><span>     return 0;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static int stir_shaken_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ast_sip_endpoint *endpoint = obj;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!strcasecmp("off", var->value)) {</span><br><span style="color: hsl(120, 100%, 40%);">+            endpoint->stir_shaken = AST_SIP_STIR_SHAKEN_OFF;</span><br><span style="color: hsl(120, 100%, 40%);">+   } else if (!strcasecmp("attest", var->value)) {</span><br><span style="color: hsl(120, 100%, 40%);">+          endpoint->stir_shaken = AST_SIP_STIR_SHAKEN_ATTEST;</span><br><span style="color: hsl(120, 100%, 40%);">+        } else if (!strcasecmp("verify", var->value)) {</span><br><span style="color: hsl(120, 100%, 40%);">+          endpoint->stir_shaken = AST_SIP_STIR_SHAKEN_VERIFY;</span><br><span style="color: hsl(120, 100%, 40%);">+        } else if (!strcasecmp("on", var->value)) {</span><br><span style="color: hsl(120, 100%, 40%);">+              endpoint->stir_shaken = AST_SIP_STIR_SHAKEN_ON;</span><br><span style="color: hsl(120, 100%, 40%);">+    } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_log(LOG_WARNING, "'%s' is not a valid value for option "</span><br><span style="color: hsl(120, 100%, 40%);">+                        "'stir_shaken' for endpoint %s\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                  var->value, ast_sorcery_object_get_id(endpoint));</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 style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static const char *stir_shaken_map[] = {</span><br><span style="color: hsl(120, 100%, 40%);">+      [AST_SIP_STIR_SHAKEN_OFF] "off",</span><br><span style="color: hsl(120, 100%, 40%);">+    [AST_SIP_STIR_SHAKEN_ATTEST] = "attest",</span><br><span style="color: hsl(120, 100%, 40%);">+    [AST_SIP_STIR_SHAKEN_VERIFY] = "verify",</span><br><span style="color: hsl(120, 100%, 40%);">+    [AST_SIP_STIR_SHAKEN_ON] = "on",</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%);">+static int stir_shaken_to_str(const void *obj, const intptr_t *args, char **buf)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ const struct ast_sip_endpoint *endpoint = obj;</span><br><span style="color: hsl(120, 100%, 40%);">+        if (ARRAY_IN_BOUNDS(endpoint->stir_shaken, stir_shaken_map)) {</span><br><span style="color: hsl(120, 100%, 40%);">+             *buf = ast_strdup(stir_shaken_map[endpoint->stir_shaken]);</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> static int group_handler(const struct aco_option *opt,</span><br><span>                      struct ast_variable *var, void *obj)</span><br><span> {</span><br><span>@@ -2153,7 +2191,7 @@</span><br><span>   ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "codec_prefs_outgoing_answer",</span><br><span>                 "prefer: pending, operation: intersect, keep: all",</span><br><span>                codec_prefs_handler, outgoing_answer_codec_prefs_to_str, NULL, 0, 0);</span><br><span style="color: hsl(0, 100%, 40%);">-   ast_sorcery_object_field_register(sip_sorcery, "endpoint", "stir_shaken", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, stir_shaken));</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "stir_shaken", "off", stir_shaken_handler, stir_shaken_to_str, NULL, 0, 0);</span><br><span>  ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_unauthenticated_options", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_unauthenticated_options));</span><br><span> </span><br><span>  if (ast_sip_initialize_sorcery_transport()) {</span><br><span>diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c</span><br><span>index b1288b5..4eb855a 100644</span><br><span>--- a/res/res_pjsip_session.c</span><br><span>+++ b/res/res_pjsip_session.c</span><br><span>@@ -4051,6 +4051,11 @@</span><br><span> {</span><br><span>         RAII_VAR(struct ast_sip_endpoint *, endpoint,</span><br><span>                        ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+    static const pj_str_t identity_str = { "Identity", 8 };</span><br><span style="color: hsl(120, 100%, 40%);">+     const pj_str_t use_identity_header_str = {</span><br><span style="color: hsl(120, 100%, 40%);">+            AST_STIR_SHAKEN_RESPONSE_STR_USE_IDENTITY_HEADER,</span><br><span style="color: hsl(120, 100%, 40%);">+             strlen(AST_STIR_SHAKEN_RESPONSE_STR_USE_IDENTITY_HEADER)</span><br><span style="color: hsl(120, 100%, 40%);">+      };</span><br><span>   pjsip_inv_session *inv_session = NULL;</span><br><span>       struct ast_sip_session *session;</span><br><span>     struct new_invite invite;</span><br><span>@@ -4060,6 +4065,14 @@</span><br><span> </span><br><span>       ast_assert(endpoint != NULL);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+     if ((endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_VERIFY) &&</span><br><span style="color: hsl(120, 100%, 40%);">+            !ast_sip_rdata_get_header_value(rdata, identity_str)) {</span><br><span style="color: hsl(120, 100%, 40%);">+               pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata,</span><br><span style="color: hsl(120, 100%, 40%);">+                    AST_STIR_SHAKEN_RESPONSE_CODE_USE_IDENTITY_HEADER, &use_identity_header_str, NULL, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_debug(3, "No Identity header when we require one\n");</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>  inv_session = pre_session_setup(rdata, endpoint);</span><br><span>    if (!inv_session) {</span><br><span>          /* pre_session_setup() returns a response on failure */</span><br><span>diff --git a/res/res_pjsip_stir_shaken.c b/res/res_pjsip_stir_shaken.c</span><br><span>index b2b2084..1bf2528 100644</span><br><span>--- a/res/res_pjsip_stir_shaken.c</span><br><span>+++ b/res/res_pjsip_stir_shaken.c</span><br><span>@@ -32,6 +32,9 @@</span><br><span> </span><br><span> #include "asterisk/res_stir_shaken.h"</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*! The Date header will not be valid after this many milliseconds (60 seconds recommended) */</span><br><span style="color: hsl(120, 100%, 40%);">+#define STIR_SHAKEN_DATE_HEADER_TIMEOUT 60000</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*!</span><br><span>  * \brief Get the attestation from the payload</span><br><span>  *</span><br><span>@@ -109,6 +112,62 @@</span><br><span>       return 0;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static int check_date_header(pjsip_rx_data *rdata)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     static const pj_str_t date_hdr_str = { "Date", 4 };</span><br><span style="color: hsl(120, 100%, 40%);">+ char *date_hdr_val;</span><br><span style="color: hsl(120, 100%, 40%);">+   struct ast_tm date_hdr_tm;</span><br><span style="color: hsl(120, 100%, 40%);">+    struct timeval date_hdr_timeval;</span><br><span style="color: hsl(120, 100%, 40%);">+      struct timeval current_timeval;</span><br><span style="color: hsl(120, 100%, 40%);">+       char *remainder;</span><br><span style="color: hsl(120, 100%, 40%);">+      char timezone[80] = { 0 };</span><br><span style="color: hsl(120, 100%, 40%);">+    int64_t time_diff;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  date_hdr_val = ast_sip_rdata_get_header_value(rdata, date_hdr_str);</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_strlen_zero(date_hdr_val)) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "Failed to get Date header from incoming INVITE for STIR/SHAKEN\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 (!(remainder = ast_strptime(date_hdr_val, "%a, %d %b %Y %T", &date_hdr_tm))) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_log(LOG_ERROR, "Failed to parse Date header\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%);">+   sscanf(remainder, "%79s", timezone);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      if (ast_strlen_zero(timezone)) {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_log(LOG_ERROR, "A timezone is required for STIR/SHAKEN Date header, but we didn't get one\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%);">+   date_hdr_timeval = ast_mktime(&date_hdr_tm, timezone);</span><br><span style="color: hsl(120, 100%, 40%);">+    current_timeval = ast_tvnow();</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      time_diff = ast_tvdiff_ms(current_timeval, date_hdr_timeval);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (time_diff < 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+               /* An INVITE from the future! */</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_log(LOG_ERROR, "STIR/SHAKEN Date header has a future date\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+    } else if (time_diff > STIR_SHAKEN_DATE_HEADER_TIMEOUT) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "STIR/SHAKEN Date header was outside of the allowable range (60 seconds)\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%);">+   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%);">+/* Send a response back and end the session */</span><br><span style="color: hsl(120, 100%, 40%);">+static void stir_shaken_inv_end_session(struct ast_sip_session *session, pjsip_rx_data *rdata, int response_code, const pj_str_t response_str)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     pjsip_tx_data *tdata;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       if (pjsip_inv_end_session(session->inv_session, response_code, &response_str, &tdata) == PJ_SUCCESS) {</span><br><span style="color: hsl(120, 100%, 40%);">+             pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+     ast_hangup(session->channel);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*!</span><br><span>  * \internal</span><br><span>  * \brief Session supplement callback on an incoming INVITE request</span><br><span>@@ -122,6 +181,27 @@</span><br><span> static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)</span><br><span> {</span><br><span>    static const pj_str_t identity_str = { "Identity", 8 };</span><br><span style="color: hsl(120, 100%, 40%);">+     const pj_str_t bad_identity_info_str = {</span><br><span style="color: hsl(120, 100%, 40%);">+              AST_STIR_SHAKEN_RESPONSE_STR_BAD_IDENTITY_INFO,</span><br><span style="color: hsl(120, 100%, 40%);">+               strlen(AST_STIR_SHAKEN_RESPONSE_STR_BAD_IDENTITY_INFO)</span><br><span style="color: hsl(120, 100%, 40%);">+        };</span><br><span style="color: hsl(120, 100%, 40%);">+    const pj_str_t unsupported_credential_str = {</span><br><span style="color: hsl(120, 100%, 40%);">+         AST_STIR_SHAKEN_RESPONSE_STR_UNSUPPORTED_CREDENTIAL,</span><br><span style="color: hsl(120, 100%, 40%);">+          strlen(AST_STIR_SHAKEN_RESPONSE_STR_UNSUPPORTED_CREDENTIAL)</span><br><span style="color: hsl(120, 100%, 40%);">+   };</span><br><span style="color: hsl(120, 100%, 40%);">+    const pj_str_t stale_date_str = {</span><br><span style="color: hsl(120, 100%, 40%);">+             AST_STIR_SHAKEN_RESPONSE_STR_STALE_DATE,</span><br><span style="color: hsl(120, 100%, 40%);">+              strlen(AST_STIR_SHAKEN_RESPONSE_STR_STALE_DATE)</span><br><span style="color: hsl(120, 100%, 40%);">+       };</span><br><span style="color: hsl(120, 100%, 40%);">+    const pj_str_t use_supported_passport_format_str = {</span><br><span style="color: hsl(120, 100%, 40%);">+          AST_STIR_SHAKEN_RESPONSE_STR_USE_SUPPORTED_PASSPORT_FORMAT,</span><br><span style="color: hsl(120, 100%, 40%);">+           strlen(AST_STIR_SHAKEN_RESPONSE_STR_USE_SUPPORTED_PASSPORT_FORMAT)</span><br><span style="color: hsl(120, 100%, 40%);">+    };</span><br><span style="color: hsl(120, 100%, 40%);">+    const pj_str_t invalid_identity_hdr_str = {</span><br><span style="color: hsl(120, 100%, 40%);">+           AST_STIR_SHAKEN_RESPONSE_STR_INVALID_IDENTITY_HEADER,</span><br><span style="color: hsl(120, 100%, 40%);">+         strlen(AST_STIR_SHAKEN_RESPONSE_STR_INVALID_IDENTITY_HEADER)</span><br><span style="color: hsl(120, 100%, 40%);">+  };</span><br><span style="color: hsl(120, 100%, 40%);">+    const pj_str_t server_internal_error_str = { "Server Internal Error", 21 };</span><br><span>        char *identity_hdr_val;</span><br><span>      char *encoded_val;</span><br><span>   struct ast_channel *chan = session->channel;</span><br><span>@@ -132,10 +212,17 @@</span><br><span>      char *algorithm;</span><br><span>     char *public_cert_url;</span><br><span>       char *attestation;</span><br><span style="color: hsl(120, 100%, 40%);">+    char *ppt;</span><br><span>   int mismatch = 0;</span><br><span>    struct ast_stir_shaken_payload *ss_payload;</span><br><span style="color: hsl(120, 100%, 40%);">+   int failure_code = 0;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-       if (!session->endpoint->stir_shaken) {</span><br><span style="color: hsl(120, 100%, 40%);">+  /* Check if this is a reinvite. If it is, we don't need to do anything */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (rdata->msg_info.to->tag.slen) {</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%);">+   if ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_VERIFY) == 0) {</span><br><span>          return 0;</span><br><span>    }</span><br><span> </span><br><span>@@ -148,50 +235,100 @@</span><br><span>       encoded_val = strtok_r(identity_hdr_val, ".", &identity_hdr_val);</span><br><span>      header = ast_base64url_decode_string(encoded_val);</span><br><span>   if (ast_strlen_zero(header)) {</span><br><span style="color: hsl(0, 100%, 40%);">-          ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);</span><br><span style="color: hsl(0, 100%, 40%);">-               return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_debug(3, "STIR/SHAKEN INVITE for %s is missing header\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                       ast_sorcery_object_get_id(session->endpoint));</span><br><span style="color: hsl(120, 100%, 40%);">+             stir_shaken_inv_end_session(session, rdata, AST_STIR_SHAKEN_RESPONSE_CODE_BAD_IDENTITY_INFO, bad_identity_info_str);</span><br><span style="color: hsl(120, 100%, 40%);">+          return 1;</span><br><span>    }</span><br><span> </span><br><span>        encoded_val = strtok_r(identity_hdr_val, ".", &identity_hdr_val);</span><br><span>      payload = ast_base64url_decode_string(encoded_val);</span><br><span>  if (ast_strlen_zero(payload)) {</span><br><span style="color: hsl(0, 100%, 40%);">-         ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);</span><br><span style="color: hsl(0, 100%, 40%);">-               return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_debug(3, "STIR/SHAKEN INVITE for %s is missing payload\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                      ast_sorcery_object_get_id(session->endpoint));</span><br><span style="color: hsl(120, 100%, 40%);">+             stir_shaken_inv_end_session(session, rdata, AST_STIR_SHAKEN_RESPONSE_CODE_BAD_IDENTITY_INFO, bad_identity_info_str);</span><br><span style="color: hsl(120, 100%, 40%);">+          return 1;</span><br><span>    }</span><br><span> </span><br><span>        /* It's fine to leave the signature encoded */</span><br><span>   signature = strtok_r(identity_hdr_val, ";", &identity_hdr_val);</span><br><span>        if (ast_strlen_zero(signature)) {</span><br><span style="color: hsl(0, 100%, 40%);">-               ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);</span><br><span style="color: hsl(0, 100%, 40%);">-               return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_debug(3, "STIR/SHAKEN INVITE for %s is missing signature\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                    ast_sorcery_object_get_id(session->endpoint));</span><br><span style="color: hsl(120, 100%, 40%);">+             stir_shaken_inv_end_session(session, rdata, AST_STIR_SHAKEN_RESPONSE_CODE_BAD_IDENTITY_INFO, bad_identity_info_str);</span><br><span style="color: hsl(120, 100%, 40%);">+          return 1;</span><br><span>    }</span><br><span> </span><br><span>        /* Trim "info=<" to get public cert URL */</span><br><span>      strtok_r(identity_hdr_val, "<", &identity_hdr_val);</span><br><span>         public_cert_url = strtok_r(identity_hdr_val, ">", &identity_hdr_val);</span><br><span style="color: hsl(0, 100%, 40%);">-  if (ast_strlen_zero(public_cert_url)) {</span><br><span style="color: hsl(0, 100%, 40%);">-         ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);</span><br><span style="color: hsl(0, 100%, 40%);">-               return 0;</span><br><span style="color: hsl(0, 100%, 40%);">-       }</span><br><span> </span><br><span>        /* Make sure the public URL is actually a URL */</span><br><span style="color: hsl(0, 100%, 40%);">-        if (!ast_begins_with(public_cert_url, "http")) {</span><br><span style="color: hsl(0, 100%, 40%);">-              ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);</span><br><span style="color: hsl(0, 100%, 40%);">-               return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+     if (ast_strlen_zero(public_cert_url) || !ast_begins_with(public_cert_url, "http")) {</span><br><span style="color: hsl(120, 100%, 40%);">+                /* RFC8224 states that if we can't acquire the credentials needed</span><br><span style="color: hsl(120, 100%, 40%);">+          * by the verification service, we should send a 436 */</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_debug(3, "STIR/SHAKEN INVITE for %s did not  have valid URL (%s)\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                    ast_sorcery_object_get_id(session->endpoint), public_cert_url);</span><br><span style="color: hsl(120, 100%, 40%);">+            stir_shaken_inv_end_session(session, rdata, AST_STIR_SHAKEN_RESPONSE_CODE_BAD_IDENTITY_INFO, bad_identity_info_str);</span><br><span style="color: hsl(120, 100%, 40%);">+          return 1;</span><br><span>    }</span><br><span> </span><br><span>        algorithm = strtok_r(identity_hdr_val, ";", &identity_hdr_val);</span><br><span>        if (ast_strlen_zero(algorithm)) {</span><br><span style="color: hsl(0, 100%, 40%);">-               ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);</span><br><span style="color: hsl(0, 100%, 40%);">-               return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+             /* RFC8224 states that if the algorithm is not specified, use ES256 */</span><br><span style="color: hsl(120, 100%, 40%);">+                algorithm = STIR_SHAKEN_ENCRYPTION_ALGORITHM;</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+              strtok_r(algorithm, "=", &algorithm);</span><br><span style="color: hsl(120, 100%, 40%);">+           if (strcmp(algorithm, STIR_SHAKEN_ENCRYPTION_ALGORITHM)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                    /* RFC8224 states that if we don't support the algorithm, send a 437 */</span><br><span style="color: hsl(120, 100%, 40%);">+                   ast_debug(3, "STIR/SHAKEN INVITE for %s uses an unsupported algorithm (%s)\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                              ast_sorcery_object_get_id(session->endpoint), algorithm);</span><br><span style="color: hsl(120, 100%, 40%);">+                  stir_shaken_inv_end_session(session, rdata, AST_STIR_SHAKEN_RESPONSE_CODE_UNSUPPORTED_CREDENTIAL, unsupported_credential_str);</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%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /* The only thing left should be ppt=shaken (which could have more values later),</span><br><span style="color: hsl(120, 100%, 40%);">+      * unless using the compact PASSport form */</span><br><span style="color: hsl(120, 100%, 40%);">+  strtok_r(identity_hdr_val, "=", &identity_hdr_val);</span><br><span style="color: hsl(120, 100%, 40%);">+     ppt = ast_strip(identity_hdr_val);</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!ast_strlen_zero(ppt) && strcmp(ppt, STIR_SHAKEN_PPT)) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "STIR/SHAKEN INVITE for %s has unsupported ppt (%s)\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                  ast_sorcery_object_get_id(session->endpoint), ppt);</span><br><span style="color: hsl(120, 100%, 40%);">+                stir_shaken_inv_end_session(session, rdata, AST_STIR_SHAKEN_RESPONSE_CODE_USE_SUPPORTED_PASSPORT_FORMAT, use_supported_passport_format_str);</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 (check_date_header(rdata)) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_debug(3, "STIR/SHAKEN INVITE for %s has old Date header\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                     ast_sorcery_object_get_id(session->endpoint));</span><br><span style="color: hsl(120, 100%, 40%);">+             stir_shaken_inv_end_session(session, rdata, AST_STIR_SHAKEN_RESPONSE_CODE_STALE_DATE, stale_date_str);</span><br><span style="color: hsl(120, 100%, 40%);">+                return 1;</span><br><span>    }</span><br><span> </span><br><span>        attestation = get_attestation_from_payload(payload);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        ss_payload = ast_stir_shaken_verify(header, payload, signature, algorithm, public_cert_url);</span><br><span style="color: hsl(120, 100%, 40%);">+  ss_payload = ast_stir_shaken_verify2(header, payload, signature, algorithm, public_cert_url, &failure_code);</span><br><span>     if (!ss_payload) {</span><br><span style="color: hsl(0, 100%, 40%);">-              ast_stir_shaken_add_verification(chan, caller_id, attestation, AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);</span><br><span style="color: hsl(0, 100%, 40%);">-                return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           if (failure_code == AST_STIR_SHAKEN_VERIFY_FAILED_TO_GET_CERT) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      /* RFC8224 states that if we can't get the credentials we need, send a 437 */</span><br><span style="color: hsl(120, 100%, 40%);">+                     ast_debug(3, "STIR/SHAKEN INVITE for %s failed to acquire cert during verification process\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                              ast_sorcery_object_get_id(session->endpoint));</span><br><span style="color: hsl(120, 100%, 40%);">+                     stir_shaken_inv_end_session(session, rdata, AST_STIR_SHAKEN_RESPONSE_CODE_UNSUPPORTED_CREDENTIAL, unsupported_credential_str);</span><br><span style="color: hsl(120, 100%, 40%);">+                } else if (failure_code == AST_STIR_SHAKEN_VERIFY_FAILED_MEMORY_ALLOC) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      ast_log(LOG_ERROR, "Failed to allocate memory during STIR/SHAKEN verification"</span><br><span style="color: hsl(120, 100%, 40%);">+                              " for %s\n", ast_sorcery_object_get_id(session->endpoint));</span><br><span style="color: hsl(120, 100%, 40%);">+                      stir_shaken_inv_end_session(session, rdata, 500, server_internal_error_str);</span><br><span style="color: hsl(120, 100%, 40%);">+          } else if (failure_code == AST_STIR_SHAKEN_VERIFY_FAILED_SIGNATURE_VALIDATION) {</span><br><span style="color: hsl(120, 100%, 40%);">+                      /* RFC8224 states that if we can't validate the signature, send a 438 */</span><br><span style="color: hsl(120, 100%, 40%);">+                  ast_debug(3, "STIR/SHAKEN INVITE for %s failed signature validation during verification process\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                         ast_sorcery_object_get_id(session->endpoint));</span><br><span style="color: hsl(120, 100%, 40%);">+                     ast_stir_shaken_add_verification(chan, caller_id, attestation, AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED);</span><br><span style="color: hsl(120, 100%, 40%);">+                      stir_shaken_inv_end_session(session, rdata, AST_STIR_SHAKEN_RESPONSE_CODE_INVALID_IDENTITY_HEADER, invalid_identity_hdr_str);</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 1;</span><br><span>    }</span><br><span>    ast_stir_shaken_payload_free(ss_payload);</span><br><span> </span><br><span>@@ -333,7 +470,7 @@</span><br><span> </span><br><span> static void stir_shaken_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">- if (!session->endpoint->stir_shaken) {</span><br><span style="color: hsl(120, 100%, 40%);">+  if ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_ATTEST) == 0) {</span><br><span>          return;</span><br><span>      }</span><br><span> </span><br><span>diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c</span><br><span>index 1d8c785..373a1a1 100644</span><br><span>--- a/res/res_stir_shaken.c</span><br><span>+++ b/res/res_stir_shaken.c</span><br><span>@@ -617,9 +617,146 @@</span><br><span>       return filename;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Verifies that the string parameters are not empty for STIR/SHAKEN verification</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 1 on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static int stir_shaken_verify_check_empty_strings(const char *header, const char *payload, const char *signature,</span><br><span style="color: hsl(120, 100%, 40%);">+        const char *algorithm, const char *public_cert_url)</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 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_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 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_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 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_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 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_strlen_zero(public_cert_url)) {</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_log(LOG_ERROR, "'public_cert_url' is required for STIR/SHAKEN verification\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%);">+   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 or set up the file path for the certificate</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note This function will allocate memory for file_path and dir_path and populate them</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 1 on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static int stir_shaken_verify_setup_file_paths(const char *public_cert_url, char **file_path, char **dir_path, int *curl)</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_cert_url);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_asprintf(dir_path, "%s/keys/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME) < 0) {</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 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%);">+            /* 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_cert_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%);">+               /* Download to the default path */</span><br><span style="color: hsl(120, 100%, 40%);">+            *file_path = run_curl(public_cert_url, *dir_path);</span><br><span style="color: hsl(120, 100%, 40%);">+            if (!(*file_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%);">+           /* 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_cert_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%);">+   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 See if the cert is expired. If it is, remove it and try downloading again if we haven't already.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 1 on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static int stir_shaken_verify_validate_cert(const char *public_cert_url, char **file_path, char *dir_path, int *curl,</span><br><span style="color: hsl(120, 100%, 40%);">+ EVP_PKEY **public_key)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     if (public_key_is_expired(public_cert_url)) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_debug(3, "Public cert '%s' is expired\n", public_cert_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_cert_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%);">+               ast_free(*file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+         *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl);</span><br><span style="color: hsl(120, 100%, 40%);">+              if (!(*file_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%);">+</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_cert_url);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_free(*file_path);</span><br><span style="color: hsl(120, 100%, 40%);">+         *file_path = curl_and_check_expiration(public_cert_url, dir_path, curl);</span><br><span style="color: hsl(120, 100%, 40%);">+              if (!(*file_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%);">+           *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_cert_url);</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%);">+</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> struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const char *payload, const char *signature,</span><br><span>     const char *algorithm, const char *public_cert_url)</span><br><span> {</span><br><span style="color: hsl(120, 100%, 40%);">+      int code = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       return ast_stir_shaken_verify2(header, payload, signature, algorithm, public_cert_url, &code);</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_verify2(const char *header, const char *payload, const char *signature,</span><br><span style="color: hsl(120, 100%, 40%);">+      const char *algorithm, const char *public_cert_url, int *failure_code)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span>    struct ast_stir_shaken_payload *ret_payload;</span><br><span>         EVP_PKEY *public_key;</span><br><span>        int curl = 0;</span><br><span>@@ -628,28 +765,7 @@</span><br><span>         RAII_VAR(char *, combined_str, NULL, ast_free);</span><br><span>      size_t combined_size;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-       if (ast_strlen_zero(header)) {</span><br><span style="color: hsl(0, 100%, 40%);">-          ast_log(LOG_ERROR, "'header' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(0, 100%, 40%);">-            return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-    }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       if (ast_strlen_zero(payload)) {</span><br><span style="color: hsl(0, 100%, 40%);">-         ast_log(LOG_ERROR, "'payload' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(0, 100%, 40%);">-           return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-    }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       if (ast_strlen_zero(signature)) {</span><br><span style="color: hsl(0, 100%, 40%);">-               ast_log(LOG_ERROR, "'signature' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(0, 100%, 40%);">-         return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-    }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       if (ast_strlen_zero(algorithm)) {</span><br><span style="color: hsl(0, 100%, 40%);">-               ast_log(LOG_ERROR, "'algorithm' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(0, 100%, 40%);">-         return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-    }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       if (ast_strlen_zero(public_cert_url)) {</span><br><span style="color: hsl(0, 100%, 40%);">-         ast_log(LOG_ERROR, "'public_cert_url' is required for STIR/SHAKEN verification\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (stir_shaken_verify_check_empty_strings(header, payload, signature, algorithm, public_cert_url)) {</span><br><span>                return NULL;</span><br><span>         }</span><br><span> </span><br><span>@@ -663,72 +779,14 @@</span><br><span>         * {configurable) directories, we already have the storage mechanism in place.</span><br><span>        * The only thing that would be left to do is pull from the configuration.</span><br><span>    */</span><br><span style="color: hsl(0, 100%, 40%);">-     file_path = get_path_to_public_key(public_cert_url);</span><br><span style="color: hsl(0, 100%, 40%);">-    if (ast_asprintf(&dir_path, "%s/keys/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME) < 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+      if (stir_shaken_verify_setup_file_paths(public_cert_url, &file_path, &dir_path, &curl)) {</span><br><span>                return NULL;</span><br><span>         }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   /* If we don't have an entry in AstDB, CURL from the provided URL */</span><br><span style="color: hsl(0, 100%, 40%);">-        if (ast_strlen_zero(file_path)) {</span><br><span style="color: hsl(0, 100%, 40%);">-               /* Remove this entry from the database, since we will be</span><br><span style="color: hsl(0, 100%, 40%);">-                 * downloading a new file anyways.</span><br><span style="color: hsl(0, 100%, 40%);">-               */</span><br><span style="color: hsl(0, 100%, 40%);">-             remove_public_key_from_astdb(public_cert_url);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-          /* Go ahead and free file_path, in case anything was allocated above */</span><br><span style="color: hsl(0, 100%, 40%);">-         ast_free(file_path);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-            /* Download to the default path */</span><br><span style="color: hsl(0, 100%, 40%);">-              file_path = run_curl(public_cert_url, dir_path);</span><br><span style="color: hsl(0, 100%, 40%);">-                if (!file_path) {</span><br><span style="color: hsl(0, 100%, 40%);">-                       return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-            }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               /* Signal that we have already downloaded a new file, no reason to do it again */</span><br><span style="color: hsl(0, 100%, 40%);">-               curl = 1;</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               /* We should have a successful download at this point, so</span><br><span style="color: hsl(0, 100%, 40%);">-                * add an entry to the database.</span><br><span style="color: hsl(0, 100%, 40%);">-                 */</span><br><span style="color: hsl(0, 100%, 40%);">-             add_public_key_to_astdb(public_cert_url, file_path);</span><br><span style="color: hsl(0, 100%, 40%);">-    }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span>    /* Check to see if the cert we downloaded (or already had) is expired */</span><br><span style="color: hsl(0, 100%, 40%);">-        if (public_key_is_expired(public_cert_url)) {</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-           ast_debug(3, "Public cert '%s' is expired\n", public_cert_url);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               remove_public_key_from_astdb(public_cert_url);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-          /* If this fails, then there's nothing we can do */</span><br><span style="color: hsl(0, 100%, 40%);">-         ast_free(file_path);</span><br><span style="color: hsl(0, 100%, 40%);">-            file_path = curl_and_check_expiration(public_cert_url, dir_path, &curl);</span><br><span style="color: hsl(0, 100%, 40%);">-            if (!file_path) {</span><br><span style="color: hsl(0, 100%, 40%);">-                       return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-            }</span><br><span style="color: hsl(0, 100%, 40%);">-       }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-       /* First attempt to read the key. If it fails, try downloading the file,</span><br><span style="color: hsl(0, 100%, 40%);">-         * unless we already did. Check for expiration again */</span><br><span style="color: hsl(0, 100%, 40%);">- public_key = stir_shaken_read_key(file_path, 0);</span><br><span style="color: hsl(0, 100%, 40%);">-        if (!public_key) {</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-              ast_debug(3, "Failed first read of public key file '%s'\n", file_path);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               remove_public_key_from_astdb(public_cert_url);</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-          ast_free(file_path);</span><br><span style="color: hsl(0, 100%, 40%);">-            file_path = curl_and_check_expiration(public_cert_url, dir_path, &curl);</span><br><span style="color: hsl(0, 100%, 40%);">-            if (!file_path) {</span><br><span style="color: hsl(0, 100%, 40%);">-                       return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-            }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-               public_key = stir_shaken_read_key(file_path, 0);</span><br><span style="color: hsl(0, 100%, 40%);">-                if (!public_key) {</span><br><span style="color: hsl(0, 100%, 40%);">-                      ast_log(LOG_ERROR, "Failed to read public key from '%s'\n", file_path);</span><br><span style="color: hsl(0, 100%, 40%);">-                       remove_public_key_from_astdb(public_cert_url);</span><br><span style="color: hsl(0, 100%, 40%);">-                  return NULL;</span><br><span style="color: hsl(0, 100%, 40%);">-            }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (stir_shaken_verify_validate_cert(public_cert_url, &file_path, dir_path, &curl, &public_key)) {</span><br><span style="color: hsl(120, 100%, 40%);">+                *failure_code = AST_STIR_SHAKEN_VERIFY_FAILED_TO_GET_CERT;</span><br><span style="color: hsl(120, 100%, 40%);">+            return NULL;</span><br><span>         }</span><br><span> </span><br><span>        /* Combine the header and payload to get the original signed message: header.payload */</span><br><span>@@ -737,11 +795,13 @@</span><br><span>      if (!combined_str) {</span><br><span>                 ast_log(LOG_ERROR, "Failed to allocate space for message to verify\n");</span><br><span>            EVP_PKEY_free(public_key);</span><br><span style="color: hsl(120, 100%, 40%);">+            *failure_code = AST_STIR_SHAKEN_VERIFY_FAILED_MEMORY_ALLOC;</span><br><span>          return NULL;</span><br><span>         }</span><br><span>    snprintf(combined_str, combined_size, "%s.%s", header, payload);</span><br><span>   if (stir_shaken_verify_signature(combined_str, signature, public_key)) {</span><br><span>             ast_log(LOG_ERROR, "Failed to verify signature\n");</span><br><span style="color: hsl(120, 100%, 40%);">+         *failure_code = AST_STIR_SHAKEN_VERIFY_FAILED_SIGNATURE_VALIDATION;</span><br><span>          EVP_PKEY_free(public_key);</span><br><span>           return NULL;</span><br><span>         }</span><br><span>@@ -752,12 +812,14 @@</span><br><span>    ret_payload = ast_calloc(1, sizeof(*ret_payload));</span><br><span>   if (!ret_payload) {</span><br><span>          ast_log(LOG_ERROR, "Failed to allocate STIR/SHAKEN payload\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             *failure_code = AST_STIR_SHAKEN_VERIFY_FAILED_MEMORY_ALLOC;</span><br><span>          return NULL;</span><br><span>         }</span><br><span> </span><br><span>        ret_payload->header = ast_json_load_string(header, NULL);</span><br><span>         if (!ret_payload->header) {</span><br><span>               ast_log(LOG_ERROR, "Failed to create JSON from header\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          *failure_code = AST_STIR_SHAKEN_VERIFY_FAILED_MEMORY_ALLOC;</span><br><span>          ast_stir_shaken_payload_free(ret_payload);</span><br><span>           return NULL;</span><br><span>         }</span><br><span>@@ -765,6 +827,7 @@</span><br><span>      ret_payload->payload = ast_json_load_string(payload, NULL);</span><br><span>       if (!ret_payload->payload) {</span><br><span>              ast_log(LOG_ERROR, "Failed to create JSON from payload\n");</span><br><span style="color: hsl(120, 100%, 40%);">+         *failure_code = AST_STIR_SHAKEN_VERIFY_FAILED_MEMORY_ALLOC;</span><br><span>          ast_stir_shaken_payload_free(ret_payload);</span><br><span>           return NULL;</span><br><span>         }</span><br><span>@@ -834,15 +897,11 @@</span><br><span>            goto cleanup;</span><br><span>        }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   /* Check the alg value for "ES256" */</span><br><span style="color: hsl(120, 100%, 40%);">+       /* Check to see if there is a value for alg */</span><br><span>       val = ast_json_string_get(ast_json_object_get(obj, "alg"));</span><br><span style="color: hsl(0, 100%, 40%);">-   if (ast_strlen_zero(val)) {</span><br><span style="color: hsl(0, 100%, 40%);">-             ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have required field 'alg'\n");</span><br><span style="color: hsl(0, 100%, 40%);">-            goto cleanup;</span><br><span style="color: hsl(0, 100%, 40%);">-   }</span><br><span style="color: hsl(0, 100%, 40%);">-       if (strcmp(val, STIR_SHAKEN_ENCRYPTION_ALGORITHM)) {</span><br><span style="color: hsl(0, 100%, 40%);">-            ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'alg' did not have "</span><br><span style="color: hsl(0, 100%, 40%);">-                        "required value '%s' (was '%s')\n", STIR_SHAKEN_ENCRYPTION_ALGORITHM, val);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!ast_strlen_zero(val) && strcmp(val, STIR_SHAKEN_ENCRYPTION_ALGORITHM)) {</span><br><span style="color: hsl(120, 100%, 40%);">+         /* If alg is not present that's fine; if it is and is not ES256, cleanup */</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have supported type for field 'alg' (was %s)\n", val);</span><br><span>                 goto cleanup;</span><br><span>        }</span><br><span> </span><br><span></span><br></pre><div style="white-space:pre-wrap"></div><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/16526">change 16526</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/+/16526"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 18 </div>
<div style="display:none"> Gerrit-Change-Id: I4ac1ecf652cd0e336006b0ca638dc826b5b1ebf7 </div>
<div style="display:none"> Gerrit-Change-Number: 16526 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </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>