Asterisk - The Open Source Telephony Project  21.4.1
res_stir_shaken.c
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>curl</depend>
21  <depend>res_curl</depend>
22  <depend>libjwt</depend>
23  <support_level>core</support_level>
24  ***/
25 
26 #define _TRACE_PREFIX_ "rss",__LINE__, ""
27 
28 #include "asterisk.h"
29 
30 #include "asterisk/app.h"
31 #include "asterisk/cli.h"
32 #include "asterisk/conversions.h"
33 #include "asterisk/module.h"
35 #include "asterisk/pbx.h"
36 
37 #include "res_stir_shaken/stir_shaken.h"
38 
39 static int tn_auth_list_nid;
40 
41 int get_tn_auth_nid(void)
42 {
43  return tn_auth_list_nid;
44 }
45 
46 /* The datastore struct holding verification information for the channel */
48  /* The identitifier for the STIR/SHAKEN verification */
49  char *identity;
50  /* The attestation value */
51  char *attestation;
52  /* The actual verification result */
53  enum ast_stir_shaken_vs_response_code verify_result;
54 };
55 
56 /*!
57  * \brief Frees a stir_shaken_datastore structure
58  *
59  * \param datastore The datastore to free
60  */
61 static void stir_datastore_free(struct stir_datastore *datastore)
62 {
63  if (!datastore) {
64  return;
65  }
66 
67  ast_free(datastore->identity);
68  ast_free(datastore->attestation);
69  ast_free(datastore);
70 }
71 
72 /*!
73  * \brief The callback to destroy a stir_shaken_datastore
74  *
75  * \param data The stir_shaken_datastore
76  */
77 static void stir_datastore_destroy_cb(void *data)
78 {
79  struct stir_datastore *datastore = data;
80  stir_datastore_free(datastore);
81 }
82 
83 /* The stir_shaken_datastore info used to add and compare stir_shaken_datastores on the channel */
84 static const struct ast_datastore_info stir_shaken_datastore_info = {
85  .type = "STIR/SHAKEN VERIFICATION",
86  .destroy = stir_datastore_destroy_cb,
87 };
88 
89 int ast_stir_shaken_add_result_to_channel(
90  struct ast_stir_shaken_vs_ctx *ctx)
91 {
93  struct ast_datastore *chan_datastore;
94  const char *chan_name;
95 
96  if (!ctx->chan) {
97  ast_log(LOG_ERROR, "Channel is required to add STIR/SHAKEN verification\n");
98  return -1;
99  }
100 
101  chan_name = ast_channel_name(ctx->chan);
102 
103  if (!ctx->identity_hdr) {
104  ast_log(LOG_ERROR, "No identity to add STIR/SHAKEN verification to channel "
105  "%s\n", chan_name);
106  return -1;
107  }
108 
109  if (!ctx->attestation) {
110  ast_log(LOG_ERROR, "Attestation cannot be NULL to add STIR/SHAKEN verification to "
111  "channel %s\n", chan_name);
112  return -1;
113  }
114 
115  stir_datastore = ast_calloc(1, sizeof(*stir_datastore));
116  if (!stir_datastore) {
117  ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore for "
118  "channel %s\n", chan_name);
119  return -1;
120  }
121 
122  stir_datastore->identity = ast_strdup(ctx->identity_hdr);
123  if (!stir_datastore->identity) {
124  ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore "
125  "identity for channel %s\n", chan_name);
126  stir_datastore_free(stir_datastore);
127  return -1;
128  }
129 
130  stir_datastore->attestation = ast_strdup(ctx->attestation);
131  if (!stir_datastore->attestation) {
132  ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore "
133  "attestation for channel %s\n", chan_name);
134  stir_datastore_free(stir_datastore);
135  return -1;
136  }
137 
138  stir_datastore->verify_result = ctx->failure_reason;
139 
140  chan_datastore = ast_datastore_alloc(&stir_shaken_datastore_info, NULL);
141  if (!chan_datastore) {
142  ast_log(LOG_ERROR, "Failed to allocate space for datastore for channel "
143  "%s\n", chan_name);
144  stir_datastore_free(stir_datastore);
145  return -1;
146  }
147 
148  chan_datastore->data = stir_datastore;
149 
150  ast_channel_lock(ctx->chan);
151  ast_channel_datastore_add(ctx->chan, chan_datastore);
152  ast_channel_unlock(ctx->chan);
153 
154  return 0;
155 }
156 
157 /*!
158  * \brief Retrieves STIR/SHAKEN verification information for the channel via dialplan.
159  * Examples:
160  *
161  * STIR_SHAKEN(count)
162  * STIR_SHAKEN(0, identity)
163  * STIR_SHAKEN(1, attestation)
164  * STIR_SHAKEN(27, verify_result)
165  *
166  * \retval -1 on failure
167  * \retval 0 on success
168  */
169 static int func_read(struct ast_channel *chan, const char *function,
170  char *data, char *buf, size_t len)
171 {
172  struct stir_datastore *stir_datastore;
173  struct ast_datastore *chan_datastore;
174  char *parse;
175  char *first;
176  char *second;
177  unsigned int target_index, current_index = 0;
179  AST_APP_ARG(first_param);
180  AST_APP_ARG(second_param);
181  );
182 
183  if (ast_strlen_zero(data)) {
184  ast_log(LOG_WARNING, "%s requires at least one argument\n", function);
185  return -1;
186  }
187 
188  if (!chan) {
189  ast_log(LOG_ERROR, "No channel for %s function\n", function);
190  return -1;
191  }
192 
193  parse = ast_strdupa(data);
194 
195  AST_STANDARD_APP_ARGS(args, parse);
196 
197  first = ast_strip(args.first_param);
198  if (ast_strlen_zero(first)) {
199  ast_log(LOG_ERROR, "An argument must be passed to %s\n", function);
200  return -1;
201  }
202 
203  second = ast_strip(args.second_param);
204 
205  /* Check if we are only looking for the number of STIR/SHAKEN verification results */
206  if (!strcasecmp(first, "count")) {
207  size_t count = 0;
208 
209  if (!ast_strlen_zero(second)) {
210  ast_log(LOG_ERROR, "%s only takes 1 paramater for 'count'\n", function);
211  return -1;
212  }
213 
214  ast_channel_lock(chan);
215  AST_LIST_TRAVERSE(ast_channel_datastores(chan), chan_datastore, entry) {
216  if (chan_datastore->info != &stir_shaken_datastore_info) {
217  continue;
218  }
219  count++;
220  }
221  ast_channel_unlock(chan);
222 
223  snprintf(buf, len, "%zu", count);
224  return 0;
225  }
226 
227  /* If we aren't doing a count, then there should be two parameters. The field
228  * we are searching for will be the second parameter. The index is the first.
229  */
230  if (ast_strlen_zero(second)) {
231  ast_log(LOG_ERROR, "Retrieving a value using %s requires two paramaters (index, value) "
232  "- only index was given\n", function);
233  return -1;
234  }
235 
236  if (ast_str_to_uint(first, &target_index)) {
237  ast_log(LOG_ERROR, "Failed to convert index %s to integer for function %s\n",
238  first, function);
239  return -1;
240  }
241 
242  /* We don't store by uid for the datastore, so just search for the specified index */
243  ast_channel_lock(chan);
244  AST_LIST_TRAVERSE(ast_channel_datastores(chan), chan_datastore, entry) {
245  if (chan_datastore->info != &stir_shaken_datastore_info) {
246  continue;
247  }
248 
249  if (current_index == target_index) {
250  break;
251  }
252 
253  current_index++;
254  }
255  ast_channel_unlock(chan);
256  if (current_index != target_index || !chan_datastore) {
257  ast_log(LOG_WARNING, "No STIR/SHAKEN results for index '%s'\n", first);
258  return -1;
259  }
260  stir_datastore = chan_datastore->data;
261 
262  if (!strcasecmp(second, "identity")) {
263  ast_copy_string(buf, stir_datastore->identity, len);
264  } else if (!strcasecmp(second, "attestation")) {
265  ast_copy_string(buf, stir_datastore->attestation, len);
266  } else if (!strcasecmp(second, "verify_result")) {
267  ast_copy_string(buf, vs_response_code_to_str(stir_datastore->verify_result), len);
268  } else {
269  ast_log(LOG_ERROR, "No such value '%s' for %s\n", second, function);
270  return -1;
271  }
272 
273  return 0;
274 }
275 
276 static struct ast_custom_function stir_shaken_function = {
277  .name = "STIR_SHAKEN",
278  .read = func_read,
279 };
280 
281 static int reload_module(void)
282 {
283  return common_config_reload();
284 }
285 
286 static int unload_module(void)
287 {
288  int res = 0;
289 
290  common_config_unload();
291  crypto_unload();
292 
293  res |= ast_custom_function_unregister(&stir_shaken_function);
294 
295  return 0;
296 }
297 
298 #define TN_AUTH_LIST_OID "1.3.6.1.5.5.7.1.26"
299 #define TN_AUTH_LIST_SHORT "TNAuthList"
300 #define TN_AUTH_LIST_LONG "TNAuthorizationList"
301 
302 static int check_for_old_config(void)
303 {
304  const char *error_msg = "There appears to be a 'stir_shaken.conf' file"
305  " with old configuration options in it. Please see the new config"
306  " file format in the configs/samples/stir_shaken.conf.sample file"
307  " in the source tree at https://github.com/asterisk/asterisk/raw/master/configs/samples/stir_shaken.conf.sample"
308  " or visit https://docs.asterisk.org/Deployment/STIR-SHAKEN for more information.";
309  RAII_VAR(struct ast_config *, cfg, NULL, ast_config_destroy);
310  struct ast_flags config_flags = { 0 };
311  char *cat = NULL;
312 
313  cfg = ast_config_load("stir_shaken.conf", config_flags);
314  if (cfg == NULL) {
315  /*
316  * They may be loading from realtime so the fact that there's
317  * no stir-shaken.conf file isn't an issue for this purpose.
318  */
320  }
321  while ((cat = ast_category_browse(cfg, cat))) {
322  const char *val;
323  if (strcasecmp(cat, "general") == 0) {
324  ast_log(LOG_ERROR, "%s\n", error_msg);
326  }
327  val = ast_variable_retrieve(cfg, cat, "type");
328  if (val && (strcasecmp(val, "store") == 0 ||
329  strcasecmp(val, "certificate") == 0)) {
330  ast_log(LOG_ERROR, "%s\n", error_msg);
332  }
333  }
334 
336 }
337 
338 static int load_module(void)
339 {
340  int res = 0;
341 
342  if (check_for_old_config()) {
344  }
345 
346  if (crypto_load()) {
347  unload_module();
349  }
350 
351  tn_auth_list_nid = crypto_register_x509_extension(TN_AUTH_LIST_OID,
352  TN_AUTH_LIST_SHORT, TN_AUTH_LIST_LONG);
353  if (tn_auth_list_nid < 0) {
354  unload_module();
356  }
357 
358  if (common_config_load()) {
359  unload_module();
361  }
362 
363  res |= ast_custom_function_register(&stir_shaken_function);
364 
365  return res;
366 }
367 
368 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "STIR/SHAKEN Module for Asterisk",
369  .support_level = AST_MODULE_SUPPORT_CORE,
370  .load = load_module,
371  .unload = unload_module,
372  .reload = reload_module,
373  .load_pri = AST_MODPRI_CHANNEL_DEPEND - 1,
374  .requires = "res_curl",
375 );
const char * name
Definition: pbx.h:119
const char * type
Definition: datastore.h:32
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
globally accessible channel datastores
Structure for a data store type.
Definition: datastore.h:31
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
Structure for a data store object.
Definition: datastore.h:64
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
Definition: extconf.c:3326
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
#define ast_config_load(filename, flags)
Load a config file.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:223
const struct ast_datastore_info * info
Definition: datastore.h:67
Conversion utility functions.
Core PBX routines and definitions.
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
static void crypto_load(int ifd, int ofd)
refresh RSA keys from file
Definition: res_crypto.c:819
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
int ast_str_to_uint(const char *str, unsigned int *res)
Convert the given string to an unsigned integer.
Definition: conversions.c:56
Structure used to handle boolean flags.
Definition: utils.h:199
void * data
Definition: datastore.h:66
Standard Command Line Interface.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
Definition: search.h:40
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
Asterisk module definitions.
#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:941
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2385
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
#define AST_APP_ARG(name)
Define an application argument.