Asterisk - The Open Source Telephony Project  GIT-master-e8cda4b
res_stir_shaken.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2020, Sangoma Technologies Corporation
5  *
6  * Kevin Harwell <kharwell@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 /*** MODULEINFO
20  <depend>crypto</depend>
21  <depend>curl</depend>
22  <depend>res_curl</depend>
23  <support_level>core</support_level>
24  ***/
25 
26 #include "asterisk.h"
27 
28 #include <openssl/evp.h>
29 
30 #include "asterisk/module.h"
31 #include "asterisk/sorcery.h"
32 #include "asterisk/time.h"
33 #include "asterisk/json.h"
34 #include "asterisk/astdb.h"
35 #include "asterisk/paths.h"
36 #include "asterisk/conversions.h"
37 #include "asterisk/pbx.h"
39 #include "asterisk/app.h"
40 #include "asterisk/test.h"
41 
45 #include "res_stir_shaken/store.h"
47 #include "res_stir_shaken/curl.h"
48 
49 /*** DOCUMENTATION
50  <configInfo name="res_stir_shaken" language="en_US">
51  <synopsis>STIR/SHAKEN module for Asterisk</synopsis>
52  <configFile name="stir_shaken.conf">
53  <configObject name="general">
54  <synopsis>STIR/SHAKEN general options</synopsis>
55  <configOption name="type">
56  <synopsis>Must be of type 'general'.</synopsis>
57  </configOption>
58  <configOption name="ca_file" default="">
59  <synopsis>File path to the certificate authority certificate</synopsis>
60  </configOption>
61  <configOption name="ca_path" default="">
62  <synopsis>File path to a chain of trust</synopsis>
63  </configOption>
64  <configOption name="cache_max_size" default="1000">
65  <synopsis>Maximum size to use for caching public keys</synopsis>
66  </configOption>
67  <configOption name="curl_timeout" default="2">
68  <synopsis>Maximum time to wait to CURL certificates</synopsis>
69  </configOption>
70  <configOption name="signature_timeout" default="15">
71  <synopsis>Amount of time a signature is valid for</synopsis>
72  </configOption>
73  </configObject>
74  <configObject name="store">
75  <synopsis>STIR/SHAKEN certificate store options</synopsis>
76  <configOption name="type">
77  <synopsis>Must be of type 'store'.</synopsis>
78  </configOption>
79  <configOption name="path" default="">
80  <synopsis>Path to a directory containing certificates</synopsis>
81  </configOption>
82  <configOption name="public_cert_url" default="">
83  <synopsis>URL to the public certificate(s)</synopsis>
84  <description><para>
85  Must be a valid http, or https, URL. The URL must also contain the ${CERTIFICATE} variable, which is used for public key name substitution.
86  For example: http://mycompany.com/${CERTIFICATE}.pub
87  </para></description>
88  </configOption>
89  </configObject>
90  <configObject name="certificate">
91  <synopsis>STIR/SHAKEN certificate options</synopsis>
92  <configOption name="type">
93  <synopsis>Must be of type 'certificate'.</synopsis>
94  </configOption>
95  <configOption name="path" default="">
96  <synopsis>File path to a certificate</synopsis>
97  </configOption>
98  <configOption name="public_cert_url" default="">
99  <synopsis>URL to the public certificate</synopsis>
100  <description><para>
101  Must be a valid http, or https, URL.
102  </para></description>
103  </configOption>
104  <configOption name="attestation">
105  <synopsis>Attestation level</synopsis>
106  </configOption>
107  <configOption name="caller_id_number" default="">
108  <synopsis>The caller ID number to match on.</synopsis>
109  </configOption>
110  </configObject>
111  </configFile>
112  </configInfo>
113  <function name="STIR_SHAKEN" language="en_US">
114  <synopsis>
115  Gets the number of STIR/SHAKEN results or a specific STIR/SHAKEN value from a result on the channel.
116  </synopsis>
117  <syntax>
118  <parameter name="index" required="true">
119  <para>The index of the STIR/SHAKEN result to get. If only 'count' is passed in, gets the number of STIR/SHAKEN results instead.</para>
120  </parameter>
121  <parameter name="value" required="false">
122  <para>The value to get from the STIR/SHAKEN result. Only used when an index is passed in (instead of 'count'). Allowable values:</para>
123  <enumlist>
124  <enum name = "identity" />
125  <enum name = "attestation" />
126  <enum name = "verify_result" />
127  </enumlist>
128  </parameter>
129  </syntax>
130  <description>
131  <para>This function will either return the number of STIR/SHAKEN identities, or return information on the specified identity.
132  To get the number of identities, just pass 'count' as the only parameter to the function. If you want to get information on a
133  specific STIR/SHAKEN identity, you can get the number of identities and then pass an index as the first parameter and one of
134  the values you would like to retrieve as the second parameter.
135  </para>
136  <example title="Get count and retrieve value">
137  same => n,NoOp(Number of STIR/SHAKEN identities: ${STIR_SHAKEN(count)})
138  same => n,NoOp(Identity ${STIR_SHAKEN(0, identity)} has attestation level ${STIR_SHAKEN(0, attestation)})
139  </example>
140  </description>
141  </function>
142  ***/
143 
145 
146 /* Used for AstDB entries */
147 #define AST_DB_FAMILY "STIR_SHAKEN"
148 
149 /* The directory name to store keys in. Appended to ast_config_DATA_DIR */
150 #define STIR_SHAKEN_DIR_NAME "stir_shaken"
151 
152 /* The maximum length for path storage */
153 #define MAX_PATH_LEN 256
154 
155 /* The default amount of time (in seconds) to use for certificate expiration
156  * if no cache data is available
157  */
158 #define EXPIRATION_BUFFER 15
159 
161  /*! The JWT header */
162  struct ast_json *header;
163  /*! The JWT payload */
164  struct ast_json *payload;
165  /*! Signature for the payload */
166  unsigned char *signature;
167  /*! The algorithm used */
168  char *algorithm;
169  /*! THe URL to the public certificate */
171 };
172 
174 {
175  return stir_shaken_sorcery;
176 }
177 
179 {
180  if (!payload) {
181  return;
182  }
183 
184  ast_json_unref(payload->header);
185  ast_json_unref(payload->payload);
186  ast_free(payload->algorithm);
187  ast_free(payload->public_cert_url);
188  ast_free(payload->signature);
189 
190  ast_free(payload);
191 }
192 
194 {
195  return payload ? payload->signature : NULL;
196 }
197 
199 {
200  return payload ? payload->public_cert_url : NULL;
201 }
202 
204 {
206 }
207 
208 /*!
209  * \brief Convert an ast_stir_shaken_verification_result to string representation
210  *
211  * \param result The result to convert
212  *
213  * \retval empty string if not a valid enum value
214  * \retval string representation of result otherwise
215  */
217 {
218  switch (result) {
220  return "Verification not present";
222  return "Signature failed";
224  return "Verification mismatch";
226  return "Verification passed";
227  default:
228  break;
229  }
230 
231  return "";
232 }
233 
234 /* The datastore struct holding verification information for the channel */
236  /* The identitifier for the STIR/SHAKEN verification */
237  char *identity;
238  /* The attestation value */
239  char *attestation;
240  /* The actual verification result */
242 };
243 
244 /*!
245  * \brief Frees a stir_shaken_datastore structure
246  *
247  * \param datastore The datastore to free
248  */
249 static void stir_shaken_datastore_free(struct stir_shaken_datastore *datastore)
250 {
251  if (!datastore) {
252  return;
253  }
254 
255  ast_free(datastore->identity);
256  ast_free(datastore->attestation);
257  ast_free(datastore);
258 }
259 
260 /*!
261  * \brief The callback to destroy a stir_shaken_datastore
262  *
263  * \param data The stir_shaken_datastore
264  */
265 static void stir_shaken_datastore_destroy_cb(void *data)
266 {
267  struct stir_shaken_datastore *datastore = data;
268  stir_shaken_datastore_free(datastore);
269 }
270 
271 /* The stir_shaken_datastore info used to add and compare stir_shaken_datastores on the channel */
273  .type = "STIR/SHAKEN VERIFICATION",
275 };
276 
277 int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *identity, const char *attestation,
279 {
280  struct stir_shaken_datastore *ss_datastore;
281  struct ast_datastore *datastore;
282  const char *chan_name;
283 
284  if (!chan) {
285  ast_log(LOG_ERROR, "Channel is required to add STIR/SHAKEN verification\n");
286  return -1;
287  }
288 
289  chan_name = ast_channel_name(chan);
290 
291  if (!identity) {
292  ast_log(LOG_ERROR, "No identity to add STIR/SHAKEN verification to channel "
293  "%s\n", chan_name);
294  return -1;
295  }
296 
297  if (!attestation) {
298  ast_log(LOG_ERROR, "Attestation cannot be NULL to add STIR/SHAKEN verification to "
299  "channel %s\n", chan_name);
300  return -1;
301  }
302 
303  ss_datastore = ast_calloc(1, sizeof(*ss_datastore));
304  if (!ss_datastore) {
305  ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore for "
306  "channel %s\n", chan_name);
307  return -1;
308  }
309 
310  ss_datastore->identity = ast_strdup(identity);
311  if (!ss_datastore->identity) {
312  ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore "
313  "identity for channel %s\n", chan_name);
314  stir_shaken_datastore_free(ss_datastore);
315  return -1;
316  }
317 
318  ss_datastore->attestation = ast_strdup(attestation);
319  if (!ss_datastore->attestation) {
320  ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore "
321  "attestation for channel %s\n", chan_name);
322  stir_shaken_datastore_free(ss_datastore);
323  return -1;
324  }
325 
326  ss_datastore->verify_result = result;
327 
328  datastore = ast_datastore_alloc(&stir_shaken_datastore_info, NULL);
329  if (!datastore) {
330  ast_log(LOG_ERROR, "Failed to allocate space for datastore for channel "
331  "%s\n", chan_name);
332  stir_shaken_datastore_free(ss_datastore);
333  return -1;
334  }
335 
336  datastore->data = ss_datastore;
337 
338  ast_channel_lock(chan);
339  ast_channel_datastore_add(chan, datastore);
340  ast_channel_unlock(chan);
341 
342  return 0;
343 }
344 
345 /*!
346  * \brief Sets the expiration for the public key based on the provided fields.
347  * If Cache-Control is present, use it. Otherwise, use Expires.
348  *
349  * \param public_cert_url The URL to the public certificate
350  * \param data The CURL callback data containing expiration data
351  */
352 static void set_public_key_expiration(const char *public_cert_url, const struct curl_cb_data *data)
353 {
354  char time_buf[32];
355  char *value;
356  struct timeval actual_expires = ast_tvnow();
357  char hash[41];
358 
359  ast_sha1_hash(hash, public_cert_url);
360 
361  value = curl_cb_data_get_cache_control(data);
362  if (!ast_strlen_zero(value)) {
363  char *str_max_age;
364 
365  str_max_age = strstr(value, "s-maxage");
366  if (!str_max_age) {
367  str_max_age = strstr(value, "max-age");
368  }
369 
370  if (str_max_age) {
371  unsigned int max_age;
372  char *equal = strchr(str_max_age, '=');
373  if (equal && !ast_str_to_uint(equal + 1, &max_age)) {
374  actual_expires.tv_sec += max_age;
375  }
376  }
377  } else {
378  value = curl_cb_data_get_expires(data);
379  if (!ast_strlen_zero(value)) {
380  struct tm expires_time;
381 
382  strptime(value, "%a, %d %b %Y %T %z", &expires_time);
383  expires_time.tm_isdst = -1;
384  actual_expires.tv_sec = mktime(&expires_time);
385  }
386  }
387 
388  if (ast_strlen_zero(value)) {
389  actual_expires.tv_sec += EXPIRATION_BUFFER;
390  }
391 
392  snprintf(time_buf, sizeof(time_buf), "%30lu", actual_expires.tv_sec);
393 
394  ast_db_put(hash, "expiration", time_buf);
395 }
396 
397 /*!
398  * \brief Check to see if the public key is expired
399  *
400  * \param public_cert_url The public cert URL
401  *
402  * \retval 1 if expired
403  * \retval 0 if not expired
404  */
405 static int public_key_is_expired(const char *public_cert_url)
406 {
407  struct timeval current_time = ast_tvnow();
408  struct timeval expires = { .tv_sec = 0, .tv_usec = 0 };
409  char expiration[32];
410  char hash[41];
411 
412  ast_sha1_hash(hash, public_cert_url);
413  ast_db_get(hash, "expiration", expiration, sizeof(expiration));
414 
415  if (ast_strlen_zero(expiration)) {
416  return 1;
417  }
418 
419  if (ast_str_to_ulong(expiration, (unsigned long *)&expires.tv_sec)) {
420  return 1;
421  }
422 
423  return ast_tvcmp(current_time, expires) == -1 ? 0 : 1;
424 }
425 
426 /*!
427  * \brief Returns the path to the downloaded file for the provided URL
428  *
429  * \param public_cert_url The public cert URL
430  *
431  * \retval Empty string if not present in AstDB
432  * \retval The file path if present in AstDB
433  */
434 static char *get_path_to_public_key(const char *public_cert_url)
435 {
436  char hash[41];
437  char file_path[MAX_PATH_LEN];
438 
439  ast_sha1_hash(hash, public_cert_url);
440 
441  ast_db_get(hash, "path", file_path, sizeof(file_path));
442 
443  if (ast_strlen_zero(file_path)) {
444  file_path[0] = '\0';
445  }
446 
447  return ast_strdup(file_path);
448 }
449 
450 /*!
451  * \brief Add the public key details and file path to AstDB
452  *
453  * \param public_cert_url The public cert URL
454  * \param filepath The path to the file
455  */
456 static void add_public_key_to_astdb(const char *public_cert_url, const char *filepath)
457 {
458  char hash[41];
459 
460  ast_sha1_hash(hash, public_cert_url);
461 
462  ast_db_put(AST_DB_FAMILY, public_cert_url, hash);
463  ast_db_put(hash, "path", filepath);
464 }
465 
466 /*!
467  * \brief Remove the public key details and associated information from AstDB
468  *
469  * \param public_cert_url The public cert URL
470  */
472 {
473  char hash[41];
474  char filepath[MAX_PATH_LEN];
475 
476  ast_sha1_hash(hash, public_cert_url);
477 
478  /* Remove this public key from storage */
479  ast_db_get(hash, "path", filepath, sizeof(filepath));
480 
481  /* Remove the actual file from the system */
482  remove(filepath);
483 
484  ast_db_del(AST_DB_FAMILY, public_cert_url);
485  ast_db_deltree(hash, NULL);
486 }
487 
488 /*!
489  * \brief Verifies the signature using a public key
490  *
491  * \param msg The payload
492  * \param signature The signature to verify
493  * \param public_key The public key used for verification
494  *
495  * \retval -1 on failure
496  * \retval 0 on success
497  */
498 static int stir_shaken_verify_signature(const char *msg, const char *signature, EVP_PKEY *public_key)
499 {
500  EVP_MD_CTX *mdctx = NULL;
501  int ret = 0;
502  unsigned char *decoded_signature;
503  size_t signature_length, decoded_signature_length;
504 
505  mdctx = EVP_MD_CTX_create();
506  if (!mdctx) {
507  ast_log(LOG_ERROR, "Failed to create Message Digest Context\n");
508  return -1;
509  }
510 
511  ret = EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, public_key);
512  if (ret != 1) {
513  ast_log(LOG_ERROR, "Failed to initialize Message Digest Context\n");
514  EVP_MD_CTX_destroy(mdctx);
515  return -1;
516  }
517 
518  ret = EVP_DigestVerifyUpdate(mdctx, (unsigned char *)msg, strlen(msg));
519  if (ret != 1) {
520  ast_log(LOG_ERROR, "Failed to update Message Digest Context\n");
521  EVP_MD_CTX_destroy(mdctx);
522  return -1;
523  }
524 
525  /* We need to decode the signature from base64 URL to bytes. Make sure we have
526  * at least enough characters for this check */
527  signature_length = strlen(signature);
528  decoded_signature_length = (signature_length * 3 / 4);
529  decoded_signature = ast_calloc(1, decoded_signature_length);
530  ast_base64url_decode(decoded_signature, signature, decoded_signature_length);
531 
532  ret = EVP_DigestVerifyFinal(mdctx, decoded_signature, decoded_signature_length);
533  if (ret != 1) {
534  ast_log(LOG_ERROR, "Failed final phase of signature verification\n");
535  EVP_MD_CTX_destroy(mdctx);
536  ast_free(decoded_signature);
537  return -1;
538  }
539 
540  EVP_MD_CTX_destroy(mdctx);
541  ast_free(decoded_signature);
542 
543  return 0;
544 }
545 
546 /*!
547  * \brief CURL the file located at public_cert_url to the specified path
548  *
549  * \note filename will need to be freed by the caller
550  *
551  * \param public_cert_url The public cert URL
552  * \param path The path to download the file to
553  *
554  * \retval NULL on failure
555  * \retval full path filename on success
556  */
557 static char *run_curl(const char *public_cert_url, const char *path)
558 {
559  struct curl_cb_data *data;
560  char *filename;
561 
562  data = curl_cb_data_create();
563  if (!data) {
564  ast_log(LOG_ERROR, "Failed to create CURL callback data\n");
565  return NULL;
566  }
567 
568  filename = curl_public_key(public_cert_url, path, data);
569  if (!filename) {
570  ast_log(LOG_ERROR, "Could not retrieve public key for '%s'\n", public_cert_url);
571  curl_cb_data_free(data);
572  return NULL;
573  }
574 
575  set_public_key_expiration(public_cert_url, data);
576  curl_cb_data_free(data);
577 
578  return filename;
579 }
580 
581 /*!
582  * \brief Downloads the public cert from public_cert_url. If curl is non-zero, that signals
583  * CURL has already been run, and we should bail here. The entry is added to AstDB as well.
584  *
585  * \note filename will need to be freed by the caller
586  *
587  * \param public_cert_url The public cert URL
588  * \param path The path to download the file to
589  * \param curl Flag signaling if we have run CURL or not
590  *
591  * \retval NULL on failure
592  * \retval full path filename on success
593  */
594 static char *curl_and_check_expiration(const char *public_cert_url, const char *path, int *curl)
595 {
596  char *filename;
597 
598  if (curl) {
599  ast_log(LOG_ERROR, "Already downloaded public key '%s'\n", path);
600  return NULL;
601  }
602 
603  filename = run_curl(public_cert_url, path);
604  if (!filename) {
605  return NULL;
606  }
607 
608  if (public_key_is_expired(public_cert_url)) {
609  ast_log(LOG_ERROR, "Newly downloaded public key '%s' is expired\n", path);
610  ast_free(filename);
611  return NULL;
612  }
613 
614  *curl = 1;
615  add_public_key_to_astdb(public_cert_url, filename);
616 
617  return filename;
618 }
619 
620 struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const char *payload, const char *signature,
621  const char *algorithm, const char *public_cert_url)
622 {
623  struct ast_stir_shaken_payload *ret_payload;
624  EVP_PKEY *public_key;
625  int curl = 0;
626  RAII_VAR(char *, file_path, NULL, ast_free);
627  RAII_VAR(char *, dir_path, NULL, ast_free);
628  RAII_VAR(char *, combined_str, NULL, ast_free);
629  size_t combined_size;
630 
631  if (ast_strlen_zero(header)) {
632  ast_log(LOG_ERROR, "'header' is required for STIR/SHAKEN verification\n");
633  return NULL;
634  }
635 
636  if (ast_strlen_zero(payload)) {
637  ast_log(LOG_ERROR, "'payload' is required for STIR/SHAKEN verification\n");
638  return NULL;
639  }
640 
641  if (ast_strlen_zero(signature)) {
642  ast_log(LOG_ERROR, "'signature' is required for STIR/SHAKEN verification\n");
643  return NULL;
644  }
645 
646  if (ast_strlen_zero(algorithm)) {
647  ast_log(LOG_ERROR, "'algorithm' is required for STIR/SHAKEN verification\n");
648  return NULL;
649  }
650 
651  if (ast_strlen_zero(public_cert_url)) {
652  ast_log(LOG_ERROR, "'public_cert_url' is required for STIR/SHAKEN verification\n");
653  return NULL;
654  }
655 
656  /* Check to see if we have already downloaded this public cert. The reason we
657  * store the file path is because:
658  *
659  * 1. If, for some reason, the default directory changes, we still know where
660  * to look for the files we already have.
661  *
662  * 2. In the future, if we want to add a way to store the certs in multiple
663  * {configurable) directories, we already have the storage mechanism in place.
664  * The only thing that would be left to do is pull from the configuration.
665  */
666  file_path = get_path_to_public_key(public_cert_url);
667  if (ast_asprintf(&dir_path, "%s/keys/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME) < 0) {
668  return NULL;
669  }
670 
671  /* If we don't have an entry in AstDB, CURL from the provided URL */
672  if (ast_strlen_zero(file_path)) {
673  /* Remove this entry from the database, since we will be
674  * downloading a new file anyways.
675  */
676  remove_public_key_from_astdb(public_cert_url);
677 
678  /* Go ahead and free file_path, in case anything was allocated above */
679  ast_free(file_path);
680 
681  /* Download to the default path */
682  file_path = run_curl(public_cert_url, dir_path);
683  if (!file_path) {
684  return NULL;
685  }
686 
687  /* Signal that we have already downloaded a new file, no reason to do it again */
688  curl = 1;
689 
690  /* We should have a successful download at this point, so
691  * add an entry to the database.
692  */
693  add_public_key_to_astdb(public_cert_url, file_path);
694  }
695 
696  /* Check to see if the cert we downloaded (or already had) is expired */
697  if (public_key_is_expired(public_cert_url)) {
698 
699  ast_debug(3, "Public cert '%s' is expired\n", public_cert_url);
700 
701  remove_public_key_from_astdb(public_cert_url);
702 
703  /* If this fails, then there's nothing we can do */
704  ast_free(file_path);
705  file_path = curl_and_check_expiration(public_cert_url, dir_path, &curl);
706  if (!file_path) {
707  return NULL;
708  }
709  }
710 
711  /* First attempt to read the key. If it fails, try downloading the file,
712  * unless we already did. Check for expiration again */
713  public_key = stir_shaken_read_key(file_path, 0);
714  if (!public_key) {
715 
716  ast_debug(3, "Failed first read of public key file '%s'\n", file_path);
717 
718  remove_public_key_from_astdb(public_cert_url);
719 
720  ast_free(file_path);
721  file_path = curl_and_check_expiration(public_cert_url, dir_path, &curl);
722  if (!file_path) {
723  return NULL;
724  }
725 
726  public_key = stir_shaken_read_key(file_path, 0);
727  if (!public_key) {
728  ast_log(LOG_ERROR, "Failed to read public key from '%s'\n", file_path);
729  remove_public_key_from_astdb(public_cert_url);
730  return NULL;
731  }
732  }
733 
734  /* Combine the header and payload to get the original signed message: header.payload */
735  combined_size = strlen(header) + strlen(payload) + 2;
736  combined_str = ast_calloc(1, combined_size);
737  if (!combined_str) {
738  ast_log(LOG_ERROR, "Failed to allocate space for message to verify\n");
739  EVP_PKEY_free(public_key);
740  return NULL;
741  }
742  snprintf(combined_str, combined_size, "%s.%s", header, payload);
743  if (stir_shaken_verify_signature(combined_str, signature, public_key)) {
744  ast_log(LOG_ERROR, "Failed to verify signature\n");
745  EVP_PKEY_free(public_key);
746  return NULL;
747  }
748 
749  /* We don't need the public key anymore */
750  EVP_PKEY_free(public_key);
751 
752  ret_payload = ast_calloc(1, sizeof(*ret_payload));
753  if (!ret_payload) {
754  ast_log(LOG_ERROR, "Failed to allocate STIR/SHAKEN payload\n");
755  return NULL;
756  }
757 
758  ret_payload->header = ast_json_load_string(header, NULL);
759  if (!ret_payload->header) {
760  ast_log(LOG_ERROR, "Failed to create JSON from header\n");
761  ast_stir_shaken_payload_free(ret_payload);
762  return NULL;
763  }
764 
765  ret_payload->payload = ast_json_load_string(payload, NULL);
766  if (!ret_payload->payload) {
767  ast_log(LOG_ERROR, "Failed to create JSON from payload\n");
768  ast_stir_shaken_payload_free(ret_payload);
769  return NULL;
770  }
771 
772  ret_payload->signature = (unsigned char *)ast_strdup(signature);
773  ret_payload->algorithm = ast_strdup(algorithm);
774  ret_payload->public_cert_url = ast_strdup(public_cert_url);
775 
776  return ret_payload;
777 }
778 
779 /*!
780  * \brief Verifies the necessary contents are in the JSON and returns a
781  * ast_stir_shaken_payload with the extracted values.
782  *
783  * \param json The JSON to verify
784  *
785  * \return ast_stir_shaken_payload on success
786  * \return NULL on failure
787  */
789 {
791  struct ast_json *obj;
792  const char *val;
793 
794  payload = ast_calloc(1, sizeof(*payload));
795  if (!payload) {
796  ast_log(LOG_ERROR, "Failed to allocate STIR/SHAKEN payload\n");
797  goto cleanup;
798  }
799 
800  /* Look through the header first */
801  obj = ast_json_object_get(json, "header");
802  if (!obj) {
803  ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have the required field 'header'\n");
804  goto cleanup;
805  }
806 
807  payload->header = ast_json_deep_copy(obj);
808  if (!payload->header) {
809  ast_log(LOG_ERROR, "STIR_SHAKEN payload failed to copy 'header'\n");
810  goto cleanup;
811  }
812 
813  /* Check the ppt value for "shaken" */
814  val = ast_json_string_get(ast_json_object_get(obj, "ppt"));
815  if (ast_strlen_zero(val)) {
816  ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have the required field 'ppt'\n");
817  goto cleanup;
818  }
819  if (strcmp(val, STIR_SHAKEN_PPT)) {
820  ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'ppt' did not have "
821  "required value '%s' (was '%s')\n", STIR_SHAKEN_PPT, val);
822  goto cleanup;
823  }
824 
825  /* Check the typ value for "passport" */
826  val = ast_json_string_get(ast_json_object_get(obj, "typ"));
827  if (ast_strlen_zero(val)) {
828  ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have the required field 'typ'\n");
829  goto cleanup;
830  }
831  if (strcmp(val, STIR_SHAKEN_TYPE)) {
832  ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'typ' did not have "
833  "required value '%s' (was '%s')\n", STIR_SHAKEN_TYPE, val);
834  goto cleanup;
835  }
836 
837  /* Check the alg value for "ES256" */
838  val = ast_json_string_get(ast_json_object_get(obj, "alg"));
839  if (ast_strlen_zero(val)) {
840  ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have required field 'alg'\n");
841  goto cleanup;
842  }
843  if (strcmp(val, STIR_SHAKEN_ENCRYPTION_ALGORITHM)) {
844  ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'alg' did not have "
845  "required value '%s' (was '%s')\n", STIR_SHAKEN_ENCRYPTION_ALGORITHM, val);
846  goto cleanup;
847  }
848 
849  payload->algorithm = ast_strdup(val);
850  if (!payload->algorithm) {
851  ast_log(LOG_ERROR, "STIR/SHAKEN payload failed to copy 'algorithm'\n");
852  goto cleanup;
853  }
854 
855  /* Now let's check the payload section */
856  obj = ast_json_object_get(json, "payload");
857  if (!obj) {
858  ast_log(LOG_ERROR, "STIR/SHAKEN payload JWT did not have required field 'payload'\n");
859  goto cleanup;
860  }
861 
862  /* Check the orig tn value for not NULL */
864  if (ast_strlen_zero(val)) {
865  ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have required field 'orig->tn'\n");
866  goto cleanup;
867  }
868 
869  /* Payload seems sane. Copy it and return on success */
870  payload->payload = ast_json_deep_copy(obj);
871  if (!payload->payload) {
872  ast_log(LOG_ERROR, "STIR/SHAKEN payload failed to copy 'payload'\n");
873  goto cleanup;
874  }
875 
876  return payload;
877 
878 cleanup:
880  return NULL;
881 }
882 
883 /*!
884  * \brief Signs the payload and returns the signature.
885  *
886  * \param json_str The string representation of the JSON
887  * \param private_key The private key used to sign the payload
888  *
889  * \retval signature on success
890  * \retval NULL on failure
891  */
892 static unsigned char *stir_shaken_sign(char *json_str, EVP_PKEY *private_key)
893 {
894  EVP_MD_CTX *mdctx = NULL;
895  int ret = 0;
896  unsigned char *encoded_signature = NULL;
897  unsigned char *signature = NULL;
898  size_t encoded_length = 0;
899  size_t signature_length = 0;
900 
901  mdctx = EVP_MD_CTX_create();
902  if (!mdctx) {
903  ast_log(LOG_ERROR, "Failed to create Message Digest Context\n");
904  goto cleanup;
905  }
906 
907  ret = EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, private_key);
908  if (ret != 1) {
909  ast_log(LOG_ERROR, "Failed to initialize Message Digest Context\n");
910  goto cleanup;
911  }
912 
913  ret = EVP_DigestSignUpdate(mdctx, json_str, strlen(json_str));
914  if (ret != 1) {
915  ast_log(LOG_ERROR, "Failed to update Message Digest Context\n");
916  goto cleanup;
917  }
918 
919  ret = EVP_DigestSignFinal(mdctx, NULL, &signature_length);
920  if (ret != 1) {
921  ast_log(LOG_ERROR, "Failed initial phase of Message Digest Context signing\n");
922  goto cleanup;
923  }
924 
925  signature = ast_calloc(1, sizeof(unsigned char) * signature_length);
926  if (!signature) {
927  ast_log(LOG_ERROR, "Failed to allocate space for signature\n");
928  goto cleanup;
929  }
930 
931  ret = EVP_DigestSignFinal(mdctx, signature, &signature_length);
932  if (ret != 1) {
933  ast_log(LOG_ERROR, "Failed final phase of Message Digest Context signing\n");
934  goto cleanup;
935  }
936 
937  /* There are 6 bits to 1 base64 URL digit, so in order to get the size of the base64 encoded
938  * signature, we need to multiply by the number of bits in a byte and divide by 6. Since
939  * there's rounding when doing base64 conversions, add 3 bytes, just in case, and account
940  * for padding. Add another byte for the NULL-terminator.
941  */
942  encoded_length = ((signature_length * 4 / 3 + 3) & ~3) + 1;
943  encoded_signature = ast_calloc(1, encoded_length);
944  if (!encoded_signature) {
945  ast_log(LOG_ERROR, "Failed to allocate space for encoded signature\n");
946  goto cleanup;
947  }
948 
949  ast_base64url_encode((char *)encoded_signature, signature, signature_length, encoded_length);
950 
951 cleanup:
952  if (mdctx) {
953  EVP_MD_CTX_destroy(mdctx);
954  }
955  ast_free(signature);
956 
957  return encoded_signature;
958 }
959 
960 /*!
961  * \brief Adds the 'x5u' (public key URL) field to the JWT.
962  *
963  * \param json The JWT
964  * \param x5u The public key URL
965  *
966  * \retval 0 on success
967  * \retval -1 on failure
968  */
969 static int stir_shaken_add_x5u(struct ast_json *json, const char *x5u)
970 {
971  struct ast_json *value;
972 
973  value = ast_json_string_create(x5u);
974  if (!value) {
975  return -1;
976  }
977 
978  return ast_json_object_set(ast_json_object_get(json, "header"), "x5u", value);
979 }
980 
981 /*!
982  * \brief Adds the 'attest' field to the JWT.
983  *
984  * \param json The JWT
985  * \param attest The value to set attest to
986  *
987  * \retval 0 on success
988  * \retval -1 on failure
989  */
990 static int stir_shaken_add_attest(struct ast_json *json, const char *attest)
991 {
992  struct ast_json *value;
993 
994  value = ast_json_string_create(attest);
995  if (!value) {
996  return -1;
997  }
998 
999  return ast_json_object_set(ast_json_object_get(json, "payload"), "attest", value);
1000 }
1001 
1002 /*!
1003  * \brief Adds the 'origid' field to the JWT.
1004  *
1005  * \param json The JWT
1006  *
1007  * \retval 0 on success
1008  * \retval -1 on failure
1009  */
1010 static int stir_shaken_add_origid(struct ast_json *json)
1011 {
1012  struct ast_json *value;
1013  char uuid_str[AST_UUID_STR_LEN];
1014 
1015  ast_uuid_generate_str(uuid_str, sizeof(uuid_str));
1016  if (strlen(uuid_str) != (AST_UUID_STR_LEN - 1)) {
1017  return -1;
1018  }
1019 
1020  value = ast_json_string_create(uuid_str);
1021 
1022  return ast_json_object_set(ast_json_object_get(json, "payload"), "origid", value);
1023 }
1024 
1025 /*!
1026  * \brief Adds the 'iat' field to the JWT.
1027  *
1028  * \param json The JWT
1029  *
1030  * \retval 0 on success
1031  * \retval -1 on failure
1032  */
1033 static int stir_shaken_add_iat(struct ast_json *json)
1034 {
1035  struct ast_json *value;
1036  struct timeval tv;
1037  int timestamp;
1038 
1039  tv = ast_tvnow();
1040  timestamp = tv.tv_sec + tv.tv_usec / 1000;
1041  value = ast_json_integer_create(timestamp);
1042 
1043  return ast_json_object_set(ast_json_object_get(json, "payload"), "iat", value);
1044 }
1045 
1047 {
1048  struct ast_stir_shaken_payload *ss_payload;
1049  unsigned char *signature;
1050  const char *public_cert_url;
1051  const char *caller_id_num;
1052  const char *header;
1053  const char *payload;
1054  struct ast_json *tmp_json;
1055  char *msg = NULL;
1056  size_t msg_len;
1057  struct stir_shaken_certificate *cert = NULL;
1058 
1059  ss_payload = stir_shaken_verify_json(json);
1060  if (!ss_payload) {
1061  return NULL;
1062  }
1063 
1064  /* From the payload section of the JSON, get the orig section, and then get
1065  * the value of tn. This will be the caller ID number */
1067  ast_json_object_get(json, "payload"), "orig"), "tn"));
1068  if (!caller_id_num) {
1069  ast_log(LOG_ERROR, "Failed to get caller ID number from JWT\n");
1070  goto cleanup;
1071  }
1072 
1074  if (!cert) {
1075  ast_log(LOG_ERROR, "Failed to retrieve certificate for caller ID "
1076  "'%s'\n", caller_id_num);
1077  goto cleanup;
1078  }
1079 
1080  public_cert_url = stir_shaken_certificate_get_public_cert_url(cert);
1081  if (stir_shaken_add_x5u(json, public_cert_url)) {
1082  ast_log(LOG_ERROR, "Failed to add 'x5u' (public cert URL) to payload\n");
1083  goto cleanup;
1084  }
1085  ss_payload->public_cert_url = ast_strdup(public_cert_url);
1086 
1088  ast_log(LOG_ERROR, "Failed to add 'attest' to payload\n");
1089  goto cleanup;
1090  }
1091 
1092  if (stir_shaken_add_origid(json)) {
1093  ast_log(LOG_ERROR, "Failed to add 'origid' to payload\n");
1094  goto cleanup;
1095  }
1096 
1097  if (stir_shaken_add_iat(json)) {
1098  ast_log(LOG_ERROR, "Failed to add 'iat' to payload\n");
1099  goto cleanup;
1100  }
1101 
1102  /* Get the header and the payload. Combine them to get the message to sign */
1103  tmp_json = ast_json_object_get(json, "header");
1104  header = ast_json_dump_string(tmp_json);
1105  tmp_json = ast_json_object_get(json, "payload");
1106  payload = ast_json_dump_string(tmp_json);
1107  msg_len = strlen(header) + strlen(payload) + 2;
1108  msg = ast_calloc(1, msg_len);
1109  if (!msg) {
1110  ast_log(LOG_ERROR, "Failed to allocate space for message to sign\n");
1111  goto cleanup;
1112  }
1113  snprintf(msg, msg_len, "%s.%s", header, payload);
1114 
1116  if (!signature) {
1117  goto cleanup;
1118  }
1119 
1120  ss_payload->signature = signature;
1121  ao2_cleanup(cert);
1122  ast_free(msg);
1123 
1124  return ss_payload;
1125 
1126 cleanup:
1127  ao2_cleanup(cert);
1128  ast_stir_shaken_payload_free(ss_payload);
1129  ast_free(msg);
1130  return NULL;
1131 }
1132 
1133 /*!
1134  * \brief Retrieves STIR/SHAKEN verification information for the channel via dialplan.
1135  * Examples:
1136  *
1137  * STIR_SHAKEN(count)
1138  * STIR_SHAKEN(0, identity)
1139  * STIR_SHAKEN(1, attestation)
1140  * STIR_SHAKEN(27, verify_result)
1141  *
1142  * \retval -1 on failure
1143  * \retval 0 on success
1144  */
1145 static int stir_shaken_read(struct ast_channel *chan, const char *function,
1146  char *data, char *buf, size_t len)
1147 {
1148  struct stir_shaken_datastore *ss_datastore;
1149  struct ast_datastore *datastore;
1150  char *parse;
1151  char *first;
1152  char *second;
1153  unsigned int target_index, current_index = 0;
1155  AST_APP_ARG(first_param);
1156  AST_APP_ARG(second_param);
1157  );
1158 
1159  if (ast_strlen_zero(data)) {
1160  ast_log(LOG_WARNING, "%s requires at least one argument\n", function);
1161  return -1;
1162  }
1163 
1164  if (!chan) {
1165  ast_log(LOG_ERROR, "No channel for %s function\n", function);
1166  return -1;
1167  }
1168 
1169  parse = ast_strdupa(data);
1170 
1171  AST_STANDARD_APP_ARGS(args, parse);
1172 
1173  first = ast_strip(args.first_param);
1174  if (ast_strlen_zero(first)) {
1175  ast_log(LOG_ERROR, "An argument must be passed to %s\n", function);
1176  return -1;
1177  }
1178 
1179  second = ast_strip(args.second_param);
1180 
1181  /* Check if we are only looking for the number of STIR/SHAKEN verification results */
1182  if (!strcasecmp(first, "count")) {
1183 
1184  size_t count = 0;
1185 
1186  if (!ast_strlen_zero(second)) {
1187  ast_log(LOG_ERROR, "%s only takes 1 paramater for 'count'\n", function);
1188  return -1;
1189  }
1190 
1191  ast_channel_lock(chan);
1192  AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
1193  if (datastore->info != &stir_shaken_datastore_info) {
1194  continue;
1195  }
1196  count++;
1197  }
1198  ast_channel_unlock(chan);
1199 
1200  snprintf(buf, len, "%zu", count);
1201  return 0;
1202  }
1203 
1204  /* If we aren't doing a count, then there should be two parameters. The field
1205  * we are searching for will be the second parameter. The index is the first.
1206  */
1207  if (ast_strlen_zero(second)) {
1208  ast_log(LOG_ERROR, "Retrieving a value using %s requires two paramaters (index, value) "
1209  "- only index was given\n", function);
1210  return -1;
1211  }
1212 
1213  if (ast_str_to_uint(first, &target_index)) {
1214  ast_log(LOG_ERROR, "Failed to convert index %s to integer for function %s\n",
1215  first, function);
1216  return -1;
1217  }
1218 
1219  /* We don't store by uid for the datastore, so just search for the specified index */
1220  ast_channel_lock(chan);
1221  AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
1222  if (datastore->info != &stir_shaken_datastore_info) {
1223  continue;
1224  }
1225 
1226  if (current_index == target_index) {
1227  break;
1228  }
1229 
1230  current_index++;
1231  }
1232  ast_channel_unlock(chan);
1233  if (current_index != target_index || !datastore) {
1234  ast_log(LOG_WARNING, "No STIR/SHAKEN results for index '%s'\n", first);
1235  return -1;
1236  }
1237  ss_datastore = datastore->data;
1238 
1239  if (!strcasecmp(second, "identity")) {
1240  ast_copy_string(buf, ss_datastore->identity, len);
1241  } else if (!strcasecmp(second, "attestation")) {
1242  ast_copy_string(buf, ss_datastore->attestation, len);
1243  } else if (!strcasecmp(second, "verify_result")) {
1245  } else {
1246  ast_log(LOG_ERROR, "No such value '%s' for %s\n", second, function);
1247  return -1;
1248  }
1249 
1250  return 0;
1251 }
1252 
1254  .name = "STIR_SHAKEN",
1255  .read = stir_shaken_read,
1256 };
1257 
1258 #ifdef TEST_FRAMEWORK
1259 
1260 static void test_stir_shaken_add_fake_astdb_entry(const char *public_cert_url, const char *file_path)
1261 {
1262  struct timeval expires = ast_tvnow();
1263  char time_buf[32];
1264  char hash[41];
1265 
1268  snprintf(time_buf, sizeof(time_buf), "%30lu", expires.tv_sec + 300);
1269 
1270  ast_db_put(hash, "expiration", time_buf);
1271 }
1272 
1273 /*!
1274  * \brief Create a private or public key certificate
1275  *
1276  * \param file_path The path of the file to create
1277  * \param private Set to 0 if public, 1 if private
1278  *
1279  * \retval -1 on failure
1280  * \retval 0 on success
1281  */
1282 static int test_stir_shaken_write_temp_key(char *file_path, int private)
1283 {
1284  FILE *file;
1285  int fd;
1286  char *data;
1287  char *type = private ? "private" : "public";
1288  char *private_data =
1289  "-----BEGIN EC PRIVATE KEY-----\n"
1290  "MHcCAQEEIC+xv2GKNTDd81vJM8rwGAGNqgklKKxz9Qejn+pcRPC1oAoGCCqGSM49\n"
1291  "AwEHoUQDQgAEq12QXu8lH295ZMZ4udKy5VV8wVgE4qSOnkdofn3hEDsh6QTKTZg9\n"
1292  "W6PncYAVnmOFRL4cTGRbmAIShN4naZk2Yg==\n"
1293  "-----END EC PRIVATE KEY-----";
1294  char *public_data =
1295  "-----BEGIN CERTIFICATE-----\n"
1296  "MIIBzDCCAXGgAwIBAgIUXDt6EC0OixT1iRSSPV3jB/zQAlQwCgYIKoZIzj0EAwIw\n"
1297  "RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\n"
1298  "dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA0MTMwNjM3MjRaFw0yMzA3MTcw\n"
1299  "NjM3MjRaMGoxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTESMBAGA1UEBwwJU29t\n"
1300  "ZXdoZXJlMRowGAYDVQQKDBFBY21lVGVsZWNvbSwgSW5jLjENMAsGA1UECwwEVk9J\n"
1301  "UDEPMA0GA1UEAwwGU0hBS0VOMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq12Q\n"
1302  "Xu8lH295ZMZ4udKy5VV8wVgE4qSOnkdofn3hEDsh6QTKTZg9W6PncYAVnmOFRL4c\n"
1303  "TGRbmAIShN4naZk2YqMaMBgwFgYIKwYBBQUHARoECjAIoAYWBDEwMDEwCgYIKoZI\n"
1304  "zj0EAwIDSQAwRgIhAMa9Ky38DgVaIgVm9Mgws/qN3zxjMQXfxEExAbDwyq/WAiEA\n"
1305  "zbC29mvtSulwbvQJ4fBdFU84cFC3Ctu1QrCeFOiZHc4=\n"
1306  "-----END CERTIFICATE-----";
1307 
1308  fd = mkstemp(file_path);
1309  if (fd < 0) {
1310  ast_log(LOG_ERROR, "Failed to create temp %s file: %s\n", type, strerror(errno));
1311  return -1;
1312  }
1313 
1314  file = fdopen(fd, "w");
1315  if (!file) {
1316  ast_log(LOG_ERROR, "Failed to create temp %s key file: %s\n", type, strerror(errno));
1317  close(fd);
1318  return -1;
1319  }
1320 
1321  data = private ? private_data : public_data;
1322  if (fputs(data, file) == EOF) {
1323  ast_log(LOG_ERROR, "Failed to write temp %s key file\n", type);
1324  fclose(file);
1325  return -1;
1326  }
1327 
1328  fclose(file);
1329 
1330  return 0;
1331 }
1332 
1333 AST_TEST_DEFINE(test_stir_shaken_sign)
1334 {
1335  char *caller_id_number = "1234567";
1336  char file_path[] = "/tmp/stir_shaken_private.XXXXXX";
1337  RAII_VAR(char *, rm_on_exit, file_path, unlink);
1338  RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
1340 
1341  switch (cmd) {
1342  case TEST_INIT:
1343  info->name = "stir_shaken_sign";
1344  info->category = "/res/res_stir_shaken/";
1345  info->summary = "STIR/SHAKEN sign unit test";
1346  info->description =
1347  "Tests signing a JWT with a private key.";
1348  return AST_TEST_NOT_RUN;
1349  case TEST_EXECUTE:
1350  break;
1351  }
1352 
1353  /* We only need a private key to sign */
1354  test_stir_shaken_write_temp_key(file_path, 1);
1355  test_stir_shaken_create_cert(caller_id_number, file_path);
1356 
1357  /* Test missing header section */
1358  json = ast_json_pack("{s: {s: {s: s}}}", "payload", "orig", "tn", caller_id_number);
1359  payload = ast_stir_shaken_sign(json);
1360  if (payload) {
1361  ast_test_status_update(test, "Signed an invalid JWT (missing 'header')\n");
1362  test_stir_shaken_cleanup_cert(caller_id_number);
1363  return AST_TEST_FAIL;
1364  }
1365 
1366  /* Test missing payload section */
1367  ast_json_free(json);
1368  json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}}", "header", "alg",
1370  "x5u", "http://testing123");
1371  payload = ast_stir_shaken_sign(json);
1372  if (payload) {
1373  ast_test_status_update(test, "Signed an invalid JWT (missing 'payload')\n");
1374  test_stir_shaken_cleanup_cert(caller_id_number);
1375  return AST_TEST_FAIL;
1376  }
1377 
1378  /* Test missing alg section */
1379  ast_json_free(json);
1380  json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "ppt",
1381  STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE, "x5u", "http://testing123", "payload",
1382  "orig", "tn", caller_id_number);
1383  payload = ast_stir_shaken_sign(json);
1384  if (payload) {
1385  ast_test_status_update(test, "Signed an invalid JWT (missing 'alg')\n");
1386  test_stir_shaken_cleanup_cert(caller_id_number);
1387  return AST_TEST_FAIL;
1388  }
1389 
1390  /* Test invalid alg value */
1391  ast_json_free(json);
1392  json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
1393  "invalid algorithm", "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE,
1394  "x5u", "http://testing123", "payload", "orig", "tn", caller_id_number);
1395  payload = ast_stir_shaken_sign(json);
1396  if (payload) {
1397  ast_test_status_update(test, "Signed an invalid JWT (wrong 'alg')\n");
1398  test_stir_shaken_cleanup_cert(caller_id_number);
1399  return AST_TEST_FAIL;
1400  }
1401 
1402  /* Test missing ppt section */
1403  ast_json_free(json);
1404  json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
1405  STIR_SHAKEN_ENCRYPTION_ALGORITHM, "typ", STIR_SHAKEN_TYPE, "x5u", "http://testing123",
1406  "payload", "orig", "tn", caller_id_number);
1407  payload = ast_stir_shaken_sign(json);
1408  if (payload) {
1409  ast_test_status_update(test, "Signed an invalid JWT (missing 'ppt')\n");
1410  test_stir_shaken_cleanup_cert(caller_id_number);
1411  return AST_TEST_FAIL;
1412  }
1413 
1414  /* Test invalid ppt value */
1415  ast_json_free(json);
1416  json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
1417  STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", "invalid ppt", "typ", STIR_SHAKEN_TYPE,
1418  "x5u", "http://testing123", "payload", "orig", "tn", caller_id_number);
1419  payload = ast_stir_shaken_sign(json);
1420  if (payload) {
1421  ast_test_status_update(test, "Signed an invalid JWT (wrong 'ppt')\n");
1422  test_stir_shaken_cleanup_cert(caller_id_number);
1423  return AST_TEST_FAIL;
1424  }
1425 
1426  /* Test missing typ section */
1427  ast_json_free(json);
1428  json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
1429  STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "x5u", "http://testing123",
1430  "payload", "orig", "tn", caller_id_number);
1431  payload = ast_stir_shaken_sign(json);
1432  if (payload) {
1433  ast_test_status_update(test, "Signed an invalid JWT (missing 'typ')\n");
1434  test_stir_shaken_cleanup_cert(caller_id_number);
1435  return AST_TEST_FAIL;
1436  }
1437 
1438  /* Test invalid typ value */
1439  ast_json_free(json);
1440  json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
1441  STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", "invalid typ",
1442  "x5u", "http://testing123", "payload", "orig", "tn", caller_id_number);
1443  payload = ast_stir_shaken_sign(json);
1444  if (payload) {
1445  ast_test_status_update(test, "Signed an invalid JWT (wrong 'typ')\n");
1446  test_stir_shaken_cleanup_cert(caller_id_number);
1447  return AST_TEST_FAIL;
1448  }
1449 
1450  /* Test missing orig section */
1451  ast_json_free(json);
1452  json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: s}}", "header", "alg",
1454  "x5u", "http://testing123", "payload", "filler", "filler");
1455  payload = ast_stir_shaken_sign(json);
1456  if (payload) {
1457  ast_test_status_update(test, "Signed an invalid JWT (missing 'orig')\n");
1458  test_stir_shaken_cleanup_cert(caller_id_number);
1459  return AST_TEST_FAIL;
1460  }
1461 
1462  /* Test missing tn section */
1463  ast_json_free(json);
1464  json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: s}}", "header", "alg",
1466  "x5u", "http://testing123", "payload", "orig", "filler");
1467  payload = ast_stir_shaken_sign(json);
1468  if (payload) {
1469  ast_test_status_update(test, "Signed an invalid JWT (missing 'tn')\n");
1470  test_stir_shaken_cleanup_cert(caller_id_number);
1471  return AST_TEST_FAIL;
1472  }
1473 
1474  /* Test valid JWT */
1475  ast_json_free(json);
1476  json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
1478  "x5u", "http://testing123", "payload", "orig", "tn", caller_id_number);
1479  payload = ast_stir_shaken_sign(json);
1480  if (!payload) {
1481  ast_test_status_update(test, "Failed to sign a valid JWT\n");
1482  test_stir_shaken_cleanup_cert(caller_id_number);
1483  return AST_TEST_FAIL;
1484  }
1485 
1486  test_stir_shaken_cleanup_cert(caller_id_number);
1487 
1488  return AST_TEST_PASS;
1489 }
1490 
1491 AST_TEST_DEFINE(test_stir_shaken_verify)
1492 {
1493  char *caller_id_number = "1234567";
1494  char *public_cert_url = "http://testing123";
1495  char *header;
1496  char *payload;
1497  struct ast_json *tmp_json;
1498  char public_path[] = "/tmp/stir_shaken_public.XXXXXX";
1499  char private_path[] = "/tmp/stir_shaken_public.XXXXXX";
1500  RAII_VAR(char *, rm_on_exit_public, public_path, unlink);
1501  RAII_VAR(char *, rm_on_exit_private, private_path, unlink);
1502  RAII_VAR(struct ast_json *, json, NULL, ast_json_free);
1504  RAII_VAR(struct ast_stir_shaken_payload *, returned_payload, NULL, ast_stir_shaken_payload_free);
1505 
1506  switch (cmd) {
1507  case TEST_INIT:
1508  info->name = "stir_shaken_verify";
1509  info->category = "/res/res_stir_shaken/";
1510  info->summary = "STIR/SHAKEN verify unit test";
1511  info->description =
1512  "Tests verifying a signature with a public key";
1513  return AST_TEST_NOT_RUN;
1514  case TEST_EXECUTE:
1515  break;
1516  }
1517 
1518  /* We need the private key to sign, but we also need the corresponding
1519  * public key to verify */
1520  test_stir_shaken_write_temp_key(public_path, 0);
1521  test_stir_shaken_write_temp_key(private_path, 1);
1522  test_stir_shaken_create_cert(caller_id_number, private_path);
1523 
1524  /* Get the signature */
1525  json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg",
1527  "x5u", public_cert_url, "payload", "orig", "tn", caller_id_number);
1528  signed_payload = ast_stir_shaken_sign(json);
1529  if (!signed_payload) {
1530  ast_test_status_update(test, "Failed to sign a valid JWT\n");
1531  test_stir_shaken_cleanup_cert(caller_id_number);
1532  return AST_TEST_FAIL;
1533  }
1534 
1535  /* Get the header and payload for ast_stir_shaken_verify */
1536  tmp_json = ast_json_object_get(json, "header");
1537  header = ast_json_dump_string(tmp_json);
1538  tmp_json = ast_json_object_get(json, "payload");
1539  payload = ast_json_dump_string(tmp_json);
1540 
1541  /* Test empty header parameter */
1542  returned_payload = ast_stir_shaken_verify("", payload, (const char *)signed_payload->signature,
1543  STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_cert_url);
1544  if (returned_payload) {
1545  ast_test_status_update(test, "Verified a signature with missing 'header'\n");
1546  test_stir_shaken_cleanup_cert(caller_id_number);
1547  return AST_TEST_FAIL;
1548  }
1549 
1550  /* Test empty payload parameter */
1551  returned_payload = ast_stir_shaken_verify(header, "", (const char *)signed_payload->signature,
1552  STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_cert_url);
1553  if (returned_payload) {
1554  ast_test_status_update(test, "Verified a signature with missing 'payload'\n");
1555  test_stir_shaken_cleanup_cert(caller_id_number);
1556  return AST_TEST_FAIL;
1557  }
1558 
1559  /* Test empty signature parameter */
1560  returned_payload = ast_stir_shaken_verify(header, payload, "",
1561  STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_cert_url);
1562  if (returned_payload) {
1563  ast_test_status_update(test, "Verified a signature with missing 'signature'\n");
1564  test_stir_shaken_cleanup_cert(caller_id_number);
1565  return AST_TEST_FAIL;
1566  }
1567 
1568  /* Test empty algorithm parameter */
1569  returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature,
1570  "", public_cert_url);
1571  if (returned_payload) {
1572  ast_test_status_update(test, "Verified a signature with missing 'algorithm'\n");
1573  test_stir_shaken_cleanup_cert(caller_id_number);
1574  return AST_TEST_FAIL;
1575  }
1576 
1577  /* Test empty public key URL */
1578  returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature,
1580  if (returned_payload) {
1581  ast_test_status_update(test, "Verified a signature with missing 'public key URL'\n");
1582  test_stir_shaken_cleanup_cert(caller_id_number);
1583  return AST_TEST_FAIL;
1584  }
1585 
1586  /* Trick the function into thinking we've already downloaded the key */
1587  test_stir_shaken_add_fake_astdb_entry(public_cert_url, public_path);
1588 
1589  /* Verify a valid signature */
1590  returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature,
1591  STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_cert_url);
1592  if (!returned_payload) {
1593  ast_test_status_update(test, "Failed to verify a valid signature\n");
1594  remove_public_key_from_astdb(public_cert_url);
1595  test_stir_shaken_cleanup_cert(caller_id_number);
1596  return AST_TEST_FAIL;
1597  }
1598 
1599  remove_public_key_from_astdb(public_cert_url);
1600 
1601  test_stir_shaken_cleanup_cert(caller_id_number);
1602 
1603  return AST_TEST_PASS;
1604 }
1605 
1606 #endif /* TEST_FRAMEWORK */
1607 
1608 static int reload_module(void)
1609 {
1610  if (stir_shaken_sorcery) {
1611  ast_sorcery_reload(stir_shaken_sorcery);
1612  }
1613 
1614  return 0;
1615 }
1616 
1617 static int unload_module(void)
1618 {
1619  int res = 0;
1620 
1624 
1625  ast_sorcery_unref(stir_shaken_sorcery);
1626  stir_shaken_sorcery = NULL;
1627 
1628  res |= ast_custom_function_unregister(&stir_shaken_function);
1629 
1630  AST_TEST_UNREGISTER(test_stir_shaken_sign);
1631  AST_TEST_UNREGISTER(test_stir_shaken_verify);
1632 
1633  return res;
1634 }
1635 
1636 static int load_module(void)
1637 {
1638  int res = 0;
1639 
1640  if (!(stir_shaken_sorcery = ast_sorcery_open())) {
1641  ast_log(LOG_ERROR, "stir/shaken - failed to open sorcery\n");
1642  return AST_MODULE_LOAD_DECLINE;
1643  }
1644 
1645  if (stir_shaken_general_load()) {
1646  unload_module();
1647  return AST_MODULE_LOAD_DECLINE;
1648  }
1649 
1650  if (stir_shaken_store_load()) {
1651  unload_module();
1652  return AST_MODULE_LOAD_DECLINE;
1653  }
1654 
1656  unload_module();
1657  return AST_MODULE_LOAD_DECLINE;
1658  }
1659 
1661 
1662  res |= ast_custom_function_register(&stir_shaken_function);
1663 
1664  AST_TEST_REGISTER(test_stir_shaken_sign);
1665  AST_TEST_REGISTER(test_stir_shaken_verify);
1666 
1667  return res;
1668 }
1669 
1671  .support_level = AST_MODULE_SUPPORT_CORE,
1672  .load = load_module,
1673  .unload = unload_module,
1674  .reload = reload_module,
1675  .load_pri = AST_MODPRI_CHANNEL_DEPEND - 1,
1676  .requires = "res_curl",
1677 );
const char * name
Definition: pbx.h:119
const char * type
Definition: datastore.h:32
struct stir_shaken_general * stir_shaken_general_get()
Retrieve the stir/shaken &#39;general&#39; configuration object.
Definition: general.c:54
static void stir_shaken_datastore_free(struct stir_shaken_datastore *datastore)
Frees a stir_shaken_datastore structure.
static const char type[]
Definition: chan_ooh323.c:109
#define ast_channel_lock(chan)
Definition: channel.h:2913
Main Channel structure associated with a channel.
struct ast_json * header
Asterisk main include file. File version handling, generic pbx functions.
int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *identity, const char *attestation, enum ast_stir_shaken_verification_result result)
Add a STIR/SHAKEN verification result to a channel.
unsigned char * signature
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:591
static int stir_shaken_add_iat(struct ast_json *json)
Adds the &#39;iat&#39; field to the JWT.
#define AST_UUID_STR_LEN
Definition: uuid.h:27
Definition: ast_expr2.c:325
static const char * stir_shaken_verification_result_to_string(enum ast_stir_shaken_verification_result result)
Convert an ast_stir_shaken_verification_result to string representation.
static const struct ast_datastore_info stir_shaken_datastore_info
Time-related functions and macros.
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
int stir_shaken_general_unload(void)
Unload time cleanup for the stir/shaken &#39;general&#39; configuration.
Definition: general.c:232
void curl_cb_data_free(struct curl_cb_data *data)
Free a curl_cb_data struct.
Definition: curl.c:51
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the &#39;standard&#39; argument separation process for an application.
static unsigned char * stir_shaken_sign(char *json_str, EVP_PKEY *private_key)
Signs the payload and returns the signature.
#define LOG_WARNING
Definition: logger.h:274
static char * run_curl(const char *public_cert_url, const char *path)
CURL the file located at public_cert_url to the specified path.
static void stir_shaken_datastore_destroy_cb(void *data)
The callback to destroy a stir_shaken_datastore.
void ast_json_free(void *p)
Asterisk&#39;s custom JSON allocator. Exposed for use by unit tests.
Definition: json.c:52
EVP_PKEY * stir_shaken_read_key(const char *path, int priv)
Reads the public (or private) key from the specified path.
Definition: stir_shaken.c:89
globally accessible channel datastores
static struct ast_sorcery * stir_shaken_sorcery
static void remove_public_key_from_astdb(const char *public_cert_url)
Remove the public key details and associated information from AstDB.
#define ast_json_dump_string(root)
Encode a JSON value to a compact string.
Definition: json.h:763
Test Framework API.
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
Full structure for sorcery.
Definition: sorcery.c:230
Structure for a data store type.
Definition: datastore.h:31
void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *payload)
Free a STIR/SHAKEN payload.
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:150
struct ast_json * ast_json_load_string(const char *input, struct ast_json_error *error)
Parse null terminated string into a JSON object or array.
Definition: json.c:546
#define EXPIRATION_BUFFER
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:243
Structure for a data store object.
Definition: datastore.h:68
unsigned char * ast_stir_shaken_payload_get_signature(const struct ast_stir_shaken_payload *payload)
Retrieve the value for &#39;signature&#39; from an ast_stir_shaken_payload.
struct ast_sorcery * ast_stir_shaken_sorcery(void)
Retrieve the stir/shaken sorcery context.
const char * args
#define NULL
Definition: resample.c:96
int value
Definition: syslog.c:37
char * ast_stir_shaken_payload_get_public_cert_url(const struct ast_stir_shaken_payload *payload)
Retrieve the value for &#39;public_cert_url&#39; from an ast_stir_shaken_payload.
const char * stir_shaken_certificate_get_attestation(struct stir_shaken_certificate *cert)
Get the attestation level associated with a certificate.
Definition: certificate.c:101
static struct ast_custom_function stir_shaken_function
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
static void add_public_key_to_astdb(const char *public_cert_url, const char *filepath)
Add the public key details and file path to AstDB.
#define AST_DB_FAMILY
int ast_json_object_set(struct ast_json *object, const char *key, struct ast_json *value)
Set a field in a JSON object.
Definition: json.c:404
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:269
unsigned int ast_stir_shaken_signature_timeout(const struct stir_shaken_general *cfg)
Retrieve the &#39;signature_timeout&#39; general configuration option value.
Definition: general.c:92
#define ast_sorcery_unref(sorcery)
Decrease the reference count of a sorcery structure.
Definition: sorcery.h:1502
int stir_shaken_certificate_load(void)
Load time initialization for the stir/shaken &#39;certificate&#39; configuration.
Definition: certificate.c:355
static char * curl_and_check_expiration(const char *public_cert_url, const char *path, int *curl)
Downloads the public cert from public_cert_url. If curl is non-zero, that signals CURL has already be...
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:444
#define ast_log
Definition: astobj2.c:42
int ast_base64url_decode(unsigned char *dst, const char *src, int max)
Decode data from base64 URL.
Definition: main/utils.c:427
static struct ast_stir_shaken_payload * stir_shaken_verify_json(struct ast_json *json)
Verifies the necessary contents are in the JSON and returns a ast_stir_shaken_payload with the extrac...
char * curl_public_key(const char *public_cert_url, const char *path, struct curl_cb_data *data)
CURL the public key from the provided URL to the specified path.
Definition: curl.c:185
Asterisk JSON abstraction layer.
Asterisk file paths, configured in asterisk.conf.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:911
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
ast_stir_shaken_verification_result
struct ast_json * ast_json_string_create(const char *value)
Construct a JSON string from value.
Definition: json.c:268
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
struct ast_stir_shaken_payload * ast_stir_shaken_sign(struct ast_json *json)
Sign a JSON STIR/SHAKEN payload.
static int stir_shaken_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len)
Retrieves STIR/SHAKEN verification information for the channel via dialplan. Examples: ...
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:219
struct ast_datastore_list * ast_channel_datastores(struct ast_channel *chan)
const struct ast_datastore_info * info
Definition: datastore.h:71
Conversion utility functions.
int stir_shaken_certificate_unload(void)
Unload time cleanup for the stir/shaken &#39;certificate&#39; configuration.
Definition: certificate.c:347
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:300
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:273
#define STIR_SHAKEN_ENCRYPTION_ALGORITHM
char * curl_cb_data_get_expires(const struct curl_cb_data *data)
Get the expires field from a curl_cb_data struct.
Definition: curl.c:72
Core PBX routines and definitions.
char * curl_cb_data_get_cache_control(const struct curl_cb_data *data)
Get the cache_control field from a curl_cb_data struct.
Definition: curl.c:63
void ast_sha1_hash(char *output, const char *input)
Produces SHA1 hash based on input string.
Definition: main/utils.c:264
const char * ast_config_AST_DATA_DIR
Definition: options.c:158
#define LOG_ERROR
Definition: logger.h:285
int ast_tvcmp(struct timeval _a, struct timeval _b)
Compres two struct timeval instances returning -1, 0, 1 if the first arg is smaller, equal or greater to the second.
Definition: time.h:128
struct stir_shaken_certificate * stir_shaken_certificate_get_by_caller_id_number(const char *caller_id_number)
Get a STIR/SHAKEN certificate by caller ID number.
Definition: certificate.c:84
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
static int stir_shaken_add_x5u(struct ast_json *json, const char *x5u)
Adds the &#39;x5u&#39; (public key URL) field to the JWT.
static int unload_module(void)
def info(msg)
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
int errno
struct sla_ringing_trunk * first
Definition: app_meetme.c:1092
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:490
static int reload_module(void)
#define ast_strlen_zero(a)
Definition: muted.c:73
#define ast_channel_unlock(chan)
Definition: channel.h:2914
int ast_base64url_encode(char *dst, const unsigned char *src, int srclen, int max)
Encode data in base64 URL.
Definition: main/utils.c:516
static void parse(struct mgcp_request *req)
Definition: chan_mgcp.c:1872
#define ast_free(a)
Definition: astmm.h:182
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:204
static int reload(void)
Definition: cdr_mysql.c:741
enum ast_stir_shaken_verification_result verify_result
#define MAX_PATH_LEN
char * ast_uuid_generate_str(char *buf, size_t size)
Generate a UUID string.
Definition: uuid.c:143
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
#define STIR_SHAKEN_PPT
int ast_db_get(const char *family, const char *key, char *value, int valuelen)
Get key value specified by family/key.
Definition: main/db.c:412
int ast_str_to_ulong(const char *str, unsigned long *res)
Convert the given string to an unsigned long.
Definition: conversions.c:80
int ast_str_to_uint(const char *str, unsigned int *res)
Convert the given string to an unsigned integer.
Definition: conversions.c:56
static void * cleanup(void *unused)
Definition: pbx_realtime.c:124
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS|AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",.support_level=AST_MODULE_SUPPORT_EXTENDED,.load=load_module,.unload=unload_module,.reload=reload,.load_pri=AST_MODPRI_CHANNEL_DEPEND,.requires="http",)
struct ast_json * payload
static void set_public_key_expiration(const char *public_cert_url, const struct curl_cb_data *data)
Sets the expiration for the public key based on the provided fields. If Cache-Control is present...
void ast_sorcery_load(const struct ast_sorcery *sorcery)
Inform any wizards to load persistent objects.
Definition: sorcery.c:1377
void ast_sorcery_reload(const struct ast_sorcery *sorcery)
Inform any wizards to reload persistent objects.
Definition: sorcery.c:1408
void * data
Definition: datastore.h:70
static int load_module(void)
#define ao2_cleanup(obj)
Definition: astobj2.h:1958
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:397
int ast_db_del(const char *family, const char *key)
Delete entry in astdb.
Definition: main/db.c:429
EVP_PKEY * stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert)
Get the private key associated with a certificate.
Definition: certificate.c:106
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:401
int stir_shaken_store_unload(void)
Unload time cleanup for the stir/shaken &#39;store&#39; configuration.
Definition: store.c:172
const char * ast_channel_name(const struct ast_channel *chan)
unsigned int ast_stir_shaken_get_signature_timeout(void)
Retrieve the value for &#39;signature_timeout&#39; from &#39;general&#39; config object.
struct curl_cb_data * curl_cb_data_create(void)
Allocate memory for a curl_cb_data struct.
Definition: curl.c:42
static PGresult * result
Definition: cel_pgsql.c:88
#define ast_sorcery_open()
Definition: sorcery.h:408
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
Abstract JSON element (object, array, string, int, ...).
static int stir_shaken_verify_signature(const char *msg, const char *signature, EVP_PKEY *public_key)
Verifies the signature using a public key.
Definition: search.h:40
int ast_db_put(const char *family, const char *key, const char *value)
Store value addressed by family/key.
Definition: main/db.c:327
#define ast_datastore_alloc(info, uid)
Definition: datastore.h:89
#define STIR_SHAKEN_TYPE
static int stir_shaken_add_attest(struct ast_json *json, const char *attest)
Adds the &#39;attest&#39; field to the JWT.
int stir_shaken_general_load(void)
Load time initialization for the stir/shaken &#39;general&#39; configuration.
Definition: general.c:248
static char * get_path_to_public_key(const char *public_cert_url)
Returns the path to the downloaded file for the provided URL.
#define STIR_SHAKEN_DIR_NAME
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
int stir_shaken_store_load(void)
Load time initialization for the stir/shaken &#39;store&#39; configuration.
Definition: store.c:180
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2386
Persistant data storage (akin to *doze registry)
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application&#39;s arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
static int public_key_is_expired(const char *public_cert_url)
Check to see if the public key is expired.
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1508
int ast_db_deltree(const char *family, const char *keytree)
Delete one or more entries in astdb.
Definition: main/db.c:457
const char * stir_shaken_certificate_get_public_cert_url(struct stir_shaken_certificate *cert)
Get the public key URL associated with a certificate.
Definition: certificate.c:96
struct ast_json * ast_json_deep_copy(const struct ast_json *value)
Copy a JSON value, and its children.
Definition: json.c:620
static int stir_shaken_add_origid(struct ast_json *json)
Adds the &#39;origid&#39; field to the JWT.
Sorcery Data Access Layer API.
#define AST_APP_ARG(name)
Define an application argument.
struct ast_json * ast_json_integer_create(intmax_t value)
Create a JSON integer.
Definition: json.c:317
struct ast_stir_shaken_payload * ast_stir_shaken_verify(const char *header, const char *payload, const char *signature, const char *algorithm, const char *public_cert_url)
Verify a JSON STIR/SHAKEN payload.