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

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">res_stir_shaken: Implemented signing of JSON payload.<br><br>This change provides functions that take in a JSON payload, verify that<br>the contents contain all the mandatory fields and required values (if<br>any), and signs the payload with the private key. Four fields are added<br>to the payload: x5u, attest, iat, and origid. As of now, these are just<br>placeholder values that will be set to actual values once the logic is<br>implemented for what to do when an actual payload is received, but the<br>functions to add these values have all been implemented and are ready to<br>use. Upon successful signing and the addition of those four values, a<br>ast_stir_shaken_payload is returned, containing other useful information<br>such as the algorithm and signature.<br><br>Change-Id: I74fa41c0640ab2a64a1a80110155bd7062f13393<br>---<br>M include/asterisk/res_stir_shaken.h<br>M res/res_stir_shaken.c<br>M res/res_stir_shaken/certificate.c<br>M res/res_stir_shaken/certificate.h<br>4 files changed, 421 insertions(+), 17 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h</span><br><span>index 0c589a9..16f0139 100644</span><br><span>--- a/include/asterisk/res_stir_shaken.h</span><br><span>+++ b/include/asterisk/res_stir_shaken.h</span><br><span>@@ -21,6 +21,10 @@</span><br><span> #include <openssl/evp.h></span><br><span> #include <openssl/pem.h></span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_stir_shaken_payload;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_json;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*!</span><br><span>  * \brief Retrieve the stir/shaken sorcery context</span><br><span>  *</span><br><span>@@ -29,12 +33,15 @@</span><br><span> struct ast_sorcery *ast_stir_shaken_sorcery(void);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(0, 100%, 40%);">- * \brief Get the private key associated with a caller id</span><br><span style="color: hsl(0, 100%, 40%);">- *</span><br><span style="color: hsl(0, 100%, 40%);">- * \param caller_id_number The caller id used to look up the private key</span><br><span style="color: hsl(0, 100%, 40%);">- *</span><br><span style="color: hsl(0, 100%, 40%);">- * \retval The private key</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Free a STIR/SHAKEN payload</span><br><span>  */</span><br><span style="color: hsl(0, 100%, 40%);">-EVP_PKEY *ast_stir_shaken_get_private_key(const char *caller_id_number);</span><br><span style="color: hsl(120, 100%, 40%);">+void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *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%);">+ * \brief Sign a JSON STIR/SHAKEN payload</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \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%);">+struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json);</span><br><span> </span><br><span> #endif /* _RES_STIR_SHAKEN_H */</span><br><span>diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c</span><br><span>index a6656d0..cb4cc82 100644</span><br><span>--- a/res/res_stir_shaken.c</span><br><span>+++ b/res/res_stir_shaken.c</span><br><span>@@ -24,6 +24,8 @@</span><br><span> </span><br><span> #include "asterisk/module.h"</span><br><span> #include "asterisk/sorcery.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/time.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/json.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>@@ -31,16 +33,386 @@</span><br><span> #include "res_stir_shaken/store.h"</span><br><span> #include "res_stir_shaken/certificate.h"</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+#define STIR_SHAKEN_ENCRYPTION_ALGORITHM "ES256"</span><br><span style="color: hsl(120, 100%, 40%);">+#define STIR_SHAKEN_PPT "shaken"</span><br><span style="color: hsl(120, 100%, 40%);">+#define STIR_SHAKEN_TYPE "passport"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static struct ast_sorcery *stir_shaken_sorcery;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_stir_shaken_payload {</span><br><span style="color: hsl(120, 100%, 40%);">+   /*! The JWT header */</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *header;</span><br><span style="color: hsl(120, 100%, 40%);">+      /*! The JWT payload */</span><br><span style="color: hsl(120, 100%, 40%);">+        struct ast_json *payload;</span><br><span style="color: hsl(120, 100%, 40%);">+     /*! Signature for the payload */</span><br><span style="color: hsl(120, 100%, 40%);">+      unsigned char *signature;</span><br><span style="color: hsl(120, 100%, 40%);">+     /*! The algorithm used */</span><br><span style="color: hsl(120, 100%, 40%);">+     char *algorithm;</span><br><span style="color: hsl(120, 100%, 40%);">+      /*! THe URL to the public key for the certificate */</span><br><span style="color: hsl(120, 100%, 40%);">+  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> struct ast_sorcery *ast_stir_shaken_sorcery(void)</span><br><span> {</span><br><span>        return stir_shaken_sorcery;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-EVP_PKEY *ast_stir_shaken_get_private_key(const char *caller_id_number)</span><br><span style="color: hsl(120, 100%, 40%);">+void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *payload)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-    return stir_shaken_certificate_get_private_key(caller_id_number);</span><br><span style="color: hsl(120, 100%, 40%);">+     if (!payload) {</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_json_unref(payload->header);</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_json_unref(payload->payload);</span><br><span style="color: hsl(120, 100%, 40%);">+  ast_free(payload->algorithm);</span><br><span style="color: hsl(120, 100%, 40%);">+      ast_free(payload->public_key_url);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_free(payload->signature);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_free(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 style="color: hsl(120, 100%, 40%);">+ * \brief Verifies the necessary contents are in the JSON and returns a</span><br><span style="color: hsl(120, 100%, 40%);">+ * ast_stir_shaken_payload with the extracted values.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json The JSON to verify</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return ast_stir_shaken_payload on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return NULL on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static struct ast_stir_shaken_payload *stir_shaken_verify_json(struct ast_json *json)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_stir_shaken_payload *payload;</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ast_json *obj;</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *val;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    payload = ast_calloc(1, sizeof(*payload));</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!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%);">+             goto cleanup;</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%);">+   /* Look through the header first */</span><br><span style="color: hsl(120, 100%, 40%);">+   obj = ast_json_object_get(json, "header");</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!obj) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have the required field 'header'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+           goto cleanup;</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%);">+   payload->header = ast_json_deep_copy(obj);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!payload->header) {</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_log(LOG_ERROR, "STIR_SHAKEN payload failed to copy 'header'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+                goto cleanup;</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 the ppt value for "shaken" */</span><br><span style="color: hsl(120, 100%, 40%);">+      val = ast_json_string_get(ast_json_object_get(obj, "ppt"));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_strlen_zero(val)) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have the required field 'ppt'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+              goto cleanup;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (strcmp(val, STIR_SHAKEN_PPT)) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'ppt' did not have "</span><br><span style="color: hsl(120, 100%, 40%);">+                      "required value '%s' (was '%s')\n", STIR_SHAKEN_PPT, val);</span><br><span style="color: hsl(120, 100%, 40%);">+          goto cleanup;</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 the typ value for "passport" */</span><br><span style="color: hsl(120, 100%, 40%);">+    val = ast_json_string_get(ast_json_object_get(obj, "typ"));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_strlen_zero(val)) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have the required field 'typ'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+              goto cleanup;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (strcmp(val, STIR_SHAKEN_TYPE)) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'typ' did not have "</span><br><span style="color: hsl(120, 100%, 40%);">+                      "required value '%s' (was '%s')\n", STIR_SHAKEN_TYPE, val);</span><br><span style="color: hsl(120, 100%, 40%);">+         goto cleanup;</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 the alg value for "ES256" */</span><br><span style="color: hsl(120, 100%, 40%);">+       val = ast_json_string_get(ast_json_object_get(obj, "alg"));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_strlen_zero(val)) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have required field 'alg'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          goto cleanup;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (strcmp(val, STIR_SHAKEN_ENCRYPTION_ALGORITHM)) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'alg' did not have "</span><br><span style="color: hsl(120, 100%, 40%);">+                      "required value '%s' (was '%s')\n", STIR_SHAKEN_ENCRYPTION_ALGORITHM, val);</span><br><span style="color: hsl(120, 100%, 40%);">+         goto cleanup;</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%);">+   payload->algorithm = ast_strdup(val);</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!payload->algorithm) {</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_log(LOG_ERROR, "STIR/SHAKEN payload failed to copy 'algorithm'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             goto cleanup;</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%);">+   /* Now let's check the payload section */</span><br><span style="color: hsl(120, 100%, 40%);">+ obj = ast_json_object_get(json, "payload");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!obj) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "STIR/SHAKEN payload JWT did not have required field 'payload'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+              goto cleanup;</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 the orig tn value for not NULL */</span><br><span style="color: hsl(120, 100%, 40%);">+    val = ast_json_string_get(ast_json_object_get(ast_json_object_get(obj, "orig"), "tn"));</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_strlen_zero(val)) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have required field 'orig->tn'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          goto cleanup;</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%);">+   /* Payload seems sane. Copy it and return on success */</span><br><span style="color: hsl(120, 100%, 40%);">+       payload->payload = ast_json_deep_copy(obj);</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!payload->payload) {</span><br><span style="color: hsl(120, 100%, 40%);">+           ast_log(LOG_ERROR, "STIR/SHAKEN payload failed to copy 'payload'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+               goto cleanup;</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 payload;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+cleanup:</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_stir_shaken_payload_free(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%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Signs the payload and returns the signature.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json_str The string representation of the JSON</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param private_key The private key used to sign the payload</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval signature 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%);">+static unsigned char *stir_shaken_sign(char *json_str, EVP_PKEY *private_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 *encoded_signature = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+      unsigned char *signature = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+      size_t encoded_length = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+    size_t signature_length = 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%);">+            goto cleanup;</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_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, private_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%);">+                goto cleanup;</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_DigestSignUpdate(mdctx, json_str, strlen(json_str));</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%);">+            goto cleanup;</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_DigestSignFinal(mdctx, NULL, &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 initial phase of Message Digest Context signing\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             goto cleanup;</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%);">+   signature = ast_calloc(1, sizeof(unsigned char) * signature_length);</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!signature) {</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_log(LOG_ERROR, "Failed to allocate space for signature\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             goto cleanup;</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_DigestSignFinal(mdctx, signature, &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 Message Digest Context signing\n");</span><br><span style="color: hsl(120, 100%, 40%);">+               goto cleanup;</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%);">+   /* There are 6 bits to 1 base64 digit, so in order to get the size of the base64 encoded</span><br><span style="color: hsl(120, 100%, 40%);">+       * signature, we need to multiply by the number of bits in a byte and divide by 6. Since</span><br><span style="color: hsl(120, 100%, 40%);">+       * there's rounding when doing base64 conversions, add 3 bytes, just in case, and account</span><br><span style="color: hsl(120, 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%);">+    */</span><br><span style="color: hsl(120, 100%, 40%);">+   encoded_length = ((signature_length * 4 / 3 + 3) & ~3) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+       encoded_signature = ast_calloc(1, encoded_length);</span><br><span style="color: hsl(120, 100%, 40%);">+    if (!encoded_signature) {</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_log(LOG_ERROR, "Failed to allocate space for encoded signature\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             goto cleanup;</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_base64encode((char *)encoded_signature, signature, signature_length, encoded_length);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+cleanup:</span><br><span style="color: hsl(120, 100%, 40%);">+ if (mdctx) {</span><br><span style="color: hsl(120, 100%, 40%);">+          EVP_MD_CTX_destroy(mdctx);</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+     ast_free(signature);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        return encoded_signature;</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 Adds the 'x5u' (public key URL) field to the JWT.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json The JWT</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param x5u The public key URL</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_add_x5u(struct ast_json *json, const char *x5u)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *value;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     value = ast_json_string_create(x5u);</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!value) {</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_json_object_set(ast_json_object_get(json, "header"), "x5u", 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%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Adds the 'attest' field to the JWT.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json The JWT</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param attest The value to set attest to</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_add_attest(struct ast_json *json, const char *attest)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_json *value;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     value = ast_json_string_create(attest);</span><br><span style="color: hsl(120, 100%, 40%);">+       if (!value) {</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_json_object_set(ast_json_object_get(json, "payload"), "attest", 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%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Adds the 'origid' field to the JWT.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json The JWT</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param origid The value to set origid to</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_add_origid(struct ast_json *json, const char *origid)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *value;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     value = ast_json_string_create(origid);</span><br><span style="color: hsl(120, 100%, 40%);">+       if (!origid) {</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_json_object_set(ast_json_object_get(json, "payload"), "origid", 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%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Adds the 'iat' field to the JWT.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json The JWT</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_add_iat(struct ast_json *json)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+        struct ast_json *value;</span><br><span style="color: hsl(120, 100%, 40%);">+       struct timeval tv;</span><br><span style="color: hsl(120, 100%, 40%);">+    int timestamp;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      tv = ast_tvnow();</span><br><span style="color: hsl(120, 100%, 40%);">+     timestamp = tv.tv_sec + tv.tv_usec / 1000;</span><br><span style="color: hsl(120, 100%, 40%);">+    value = ast_json_integer_create(timestamp);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return ast_json_object_set(ast_json_object_get(json, "payload"), "iat", 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%);">+struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      struct ast_stir_shaken_payload *payload;</span><br><span style="color: hsl(120, 100%, 40%);">+      unsigned char *signature;</span><br><span style="color: hsl(120, 100%, 40%);">+     const char *caller_id_num;</span><br><span style="color: hsl(120, 100%, 40%);">+    char *json_str = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+        struct stir_shaken_certificate *cert = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        payload = stir_shaken_verify_json(json);</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!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%);">+   /* From the payload section of the JSON, get the orig section, and then get</span><br><span style="color: hsl(120, 100%, 40%);">+    * the value of tn. This will be the caller ID number */</span><br><span style="color: hsl(120, 100%, 40%);">+      caller_id_num = ast_json_string_get(ast_json_object_get(ast_json_object_get(</span><br><span style="color: hsl(120, 100%, 40%);">+                  ast_json_object_get(json, "payload"), "orig"), "tn"));</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!caller_id_num) {</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_log(LOG_ERROR, "Failed to get caller ID number from JWT\n");</span><br><span style="color: hsl(120, 100%, 40%);">+            goto cleanup;</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%);">+   cert = stir_shaken_certificate_get_by_caller_id_number(caller_id_num);</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!cert) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_log(LOG_ERROR, "Failed to retrieve certificate for caller ID "</span><br><span style="color: hsl(120, 100%, 40%);">+                  "'%s'\n", caller_id_num);</span><br><span style="color: hsl(120, 100%, 40%);">+           goto cleanup;</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_add_x5u(json, stir_shaken_certificate_get_public_key_url(cert))) {</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_log(LOG_ERROR, "Failed to add 'x5u' (public key URL) to payload\n");</span><br><span style="color: hsl(120, 100%, 40%);">+            goto cleanup;</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%);">+   /* TODO: This is just a placeholder for adding 'attest', 'iat', and</span><br><span style="color: hsl(120, 100%, 40%);">+    * 'origid' to the payload. Later, additional logic will need to be</span><br><span style="color: hsl(120, 100%, 40%);">+    * added to determine what these values actually are, but the functions</span><br><span style="color: hsl(120, 100%, 40%);">+        * themselves are ready to go.</span><br><span style="color: hsl(120, 100%, 40%);">+         */</span><br><span style="color: hsl(120, 100%, 40%);">+   if (stir_shaken_add_attest(json, "B")) {</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_log(LOG_ERROR, "Failed to add 'attest' to payload\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          goto cleanup;</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_add_origid(json, "asterisk")) {</span><br><span style="color: hsl(120, 100%, 40%);">+             ast_log(LOG_ERROR, "Failed to add 'origid' to payload\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          goto cleanup;</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_add_iat(json)) {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_log(LOG_ERROR, "Failed to add 'iat' to payload\n");</span><br><span style="color: hsl(120, 100%, 40%);">+             goto cleanup;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   json_str = ast_json_dump_string(json);</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!json_str) {</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_log(LOG_ERROR, "Failed to convert JSON to string\n");</span><br><span style="color: hsl(120, 100%, 40%);">+           goto cleanup;</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%);">+   signature = stir_shaken_sign(json_str, stir_shaken_certificate_get_private_key(cert));</span><br><span style="color: hsl(120, 100%, 40%);">+        if (!signature) {</span><br><span style="color: hsl(120, 100%, 40%);">+             goto cleanup;</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%);">+   payload->signature = signature;</span><br><span style="color: hsl(120, 100%, 40%);">+    ao2_cleanup(cert);</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_json_free(json_str);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    return payload;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+cleanup:</span><br><span style="color: hsl(120, 100%, 40%);">+   ao2_cleanup(cert);</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_stir_shaken_payload_free(payload);</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_json_free(json_str);</span><br><span style="color: hsl(120, 100%, 40%);">+      return NULL;</span><br><span> }</span><br><span> </span><br><span> static int reload_module(void)</span><br><span>diff --git a/res/res_stir_shaken/certificate.c b/res/res_stir_shaken/certificate.c</span><br><span>index 799cea1..812fc1e 100644</span><br><span>--- a/res/res_stir_shaken/certificate.c</span><br><span>+++ b/res/res_stir_shaken/certificate.c</span><br><span>@@ -79,23 +79,34 @@</span><br><span>       return cfg;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-EVP_PKEY *stir_shaken_certificate_get_private_key(const char *caller_id_number)</span><br><span style="color: hsl(120, 100%, 40%);">+struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(const char *caller_id_number)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">- struct stir_shaken_certificate *cert;</span><br><span>        struct ast_variable fields = {</span><br><span>               .name = "caller_id_number",</span><br><span>                .value = caller_id_number,</span><br><span>           .next = NULL,</span><br><span>        };</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-  cert = ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(),</span><br><span style="color: hsl(120, 100%, 40%);">+      return ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(),</span><br><span>             "certificate", AST_RETRIEVE_FLAG_DEFAULT, &fields);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        if (cert) {</span><br><span style="color: hsl(0, 100%, 40%);">-             return cert->private_key;</span><br><span style="color: hsl(120, 100%, 40%);">+const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!cert) {</span><br><span style="color: hsl(120, 100%, 40%);">+          return NULL;</span><br><span>         }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   return NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+  return cert->public_key_url;</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_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!cert) {</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 cert->private_key;</span><br><span> }</span><br><span> </span><br><span> static int stir_shaken_certificate_apply(const struct ast_sorcery *sorcery, void *obj)</span><br><span>diff --git a/res/res_stir_shaken/certificate.h b/res/res_stir_shaken/certificate.h</span><br><span>index 9d6ec73..fda3bf1 100644</span><br><span>--- a/res/res_stir_shaken/certificate.h</span><br><span>+++ b/res/res_stir_shaken/certificate.h</span><br><span>@@ -22,15 +22,29 @@</span><br><span> </span><br><span> struct ast_sorcery;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+struct stir_shaken_certificate;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(const char *caller_id_number);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*!</span><br><span style="color: hsl(0, 100%, 40%);">- * \brief Get the private key associated with a caller id</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Get the public key URL associated with a certificate</span><br><span>  *</span><br><span style="color: hsl(0, 100%, 40%);">- * \param caller_id_number The caller id used to look up the private key</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param cert The certificate to get the public key URL from</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 The public key URL on success</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert);</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 private key associated with a certificate</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param cert The certificate to get the private key from</span><br><span>  *</span><br><span>  * \retval NULL on failure</span><br><span>  * \retval The private key on success</span><br><span>  */</span><br><span style="color: hsl(0, 100%, 40%);">-EVP_PKEY *stir_shaken_certificate_get_private_key(const char *caller_id_number);</span><br><span style="color: hsl(120, 100%, 40%);">+EVP_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert);</span><br><span> </span><br><span> /*!</span><br><span>  * \brief Load time initialization for the stir/shaken 'certificate' configuration</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/15026">change 15026</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/+/15026"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 16 </div>
<div style="display:none"> Gerrit-Change-Id: I74fa41c0640ab2a64a1a80110155bd7062f13393 </div>
<div style="display:none"> Gerrit-Change-Number: 15026 </div>
<div style="display:none"> Gerrit-PatchSet: 2 </div>
<div style="display:none"> Gerrit-Owner: Joshua Colp <jcolp@sangoma.com> </div>
<div style="display:none"> Gerrit-Reviewer: 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-MessageType: merged </div>