<p>Friendly Automation <strong>submitted</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/14031">View Change</a></p><div style="white-space:pre-wrap">Approvals:
Joshua Colp: Looks good to me, but someone else must approve
Kevin Harwell: Looks good to me, but someone else must approve
George Joseph: Looks good to me, approved
Friendly Automation: 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/+/14031">change 14031</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/+/14031"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I74fa41c0640ab2a64a1a80110155bd7062f13393 </div>
<div style="display:none"> Gerrit-Change-Number: 14031 </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-CC: Corey Farrell <git@cfware.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>