Asterisk - The Open Source Telephony Project  21.4.1
verification.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2023, Sangoma Technologies Corporation
5  *
6  * George Joseph <gjoseph@sangoma.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 #include <curl/curl.h>
19 #include <sys/stat.h>
20 
21 #include <jwt.h>
22 #include <jansson.h>
23 #include <regex.h>
24 
25 #include "asterisk.h"
26 
27 #define _TRACE_PREFIX_ "v",__LINE__, ""
28 
29 #include "asterisk/channel.h"
30 #include "asterisk/cli.h"
31 #include "asterisk/config.h"
32 #include "asterisk/module.h"
33 #include "asterisk/sorcery.h"
34 #include "asterisk/astdb.h"
35 #include "asterisk/conversions.h"
36 #include "asterisk/utils.h"
37 #include "asterisk/paths.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/acl.h"
40 #include "asterisk/time.h"
41 #include "asterisk/localtime.h"
42 #include "asterisk/crypto.h"
43 #include "asterisk/json.h"
44 
45 #include "stir_shaken.h"
46 
47 #define AST_DB_FAMILY "STIR_SHAKEN"
48 
49 static regex_t url_match_regex;
50 
51 /* Certificates should begin with this */
52 #define BEGIN_CERTIFICATE_STR "-----BEGIN CERTIFICATE-----"
53 
54 static const char *vs_rc_map[] = {
55  [AST_STIR_SHAKEN_VS_SUCCESS] = "success",
56  [AST_STIR_SHAKEN_VS_DISABLED] = "disabled",
57  [AST_STIR_SHAKEN_VS_INVALID_ARGUMENTS] = "invalid_arguments",
58  [AST_STIR_SHAKEN_VS_INTERNAL_ERROR] = "internal_error",
59  [AST_STIR_SHAKEN_VS_NO_IDENTITY_HDR] = "missing_identity_hdr",
60  [AST_STIR_SHAKEN_VS_NO_DATE_HDR] = "missing_date_hdr",
61  [AST_STIR_SHAKEN_VS_DATE_HDR_PARSE_FAILURE] = "date_hdr_parse_failure",
62  [AST_STIR_SHAKEN_VS_DATE_HDR_EXPIRED] = "date_hdr_range_error",
63  [AST_STIR_SHAKEN_VS_NO_JWT_HDR] = "missing_jwt_hdr",
64  [AST_STIR_SHAKEN_VS_CERT_CACHE_MISS] = "cert_cache_miss",
65  [AST_STIR_SHAKEN_VS_CERT_CACHE_INVALID] = "cert_cache_invalid",
66  [AST_STIR_SHAKEN_VS_CERT_CACHE_EXPIRED] = "cert_cache_expired",
67  [AST_STIR_SHAKEN_VS_CERT_RETRIEVAL_FAILURE] = "cert_retrieval_failure",
68  [AST_STIR_SHAKEN_VS_CERT_CONTENTS_INVALID] = "cert_contents_invalid",
69  [AST_STIR_SHAKEN_VS_CERT_NOT_TRUSTED] = "cert_not_trusted",
70  [AST_STIR_SHAKEN_VS_CERT_DATE_INVALID] = "cert_date_failure",
71  [AST_STIR_SHAKEN_VS_CERT_NO_TN_AUTH_EXT] = "cert_no_tn_auth_ext",
72  [AST_STIR_SHAKEN_VS_CERT_NO_SPC_IN_TN_AUTH_EXT] = "cert_no_spc_in_auth_ext",
73  [AST_STIR_SHAKEN_VS_NO_RAW_KEY] = "no_raw_key",
74  [AST_STIR_SHAKEN_VS_SIGNATURE_VALIDATION] = "signature_validation",
75  [AST_STIR_SHAKEN_VS_NO_IAT] = "missing_iat",
76  [AST_STIR_SHAKEN_VS_IAT_EXPIRED] = "iat_range_error",
77  [AST_STIR_SHAKEN_VS_INVALID_OR_NO_PPT] = "invalid_or_no_ppt",
78  [AST_STIR_SHAKEN_VS_INVALID_OR_NO_ALG] = "invalid_or_no_alg",
79  [AST_STIR_SHAKEN_VS_INVALID_OR_NO_TYP] = "invalid_or_no_typ",
80  [AST_STIR_SHAKEN_VS_INVALID_OR_NO_GRANTS] = "invalid_or_no_grants",
81  [AST_STIR_SHAKEN_VS_INVALID_OR_NO_ATTEST] = "invalid_or_no_attest",
82  [AST_STIR_SHAKEN_VS_NO_ORIGID] = "missing_origid",
83  [AST_STIR_SHAKEN_VS_NO_ORIG_TN] = "missing_orig_tn",
84  [AST_STIR_SHAKEN_VS_CID_ORIG_TN_MISMATCH] = "cid_orig_tn_mismatch",
85  [AST_STIR_SHAKEN_VS_NO_DEST_TN] = "missing_dest_tn",
86  [AST_STIR_SHAKEN_VS_INVALID_HEADER] = "invalid_header",
87  [AST_STIR_SHAKEN_VS_INVALID_GRANT] = "invalid_grant",
88 };
89 
90 const char *vs_response_code_to_str(
91  enum ast_stir_shaken_vs_response_code vs_rc)
92 {
93  return ARRAY_IN_BOUNDS(vs_rc, vs_rc_map) ?
94  vs_rc_map[vs_rc] : NULL;
95 }
96 
97 static void cleanup_cert_from_astdb_and_fs(
98  struct ast_stir_shaken_vs_ctx *ctx)
99 {
100  if (ast_db_exists(ctx->hash_family, "path") || ast_db_exists(ctx->hash_family, "expiration")) {
101  ast_db_deltree(ctx->hash_family, NULL);
102  }
103 
104  if (ast_db_exists(ctx->url_family, ctx->public_url)) {
105  ast_db_del(ctx->url_family, ctx->public_url);
106  }
107 
108  /* Remove the actual file from the system */
109  remove(ctx->filename);
110 }
111 
112 static int add_cert_expiration_to_astdb(struct ast_stir_shaken_vs_ctx *cert,
113  const char *cache_control_header, const char *expires_header)
114 {
115  RAII_VAR(struct verification_cfg *, cfg, vs_get_cfg(), ao2_cleanup);
116 
117  char time_buf[32];
118  time_t current_time = time(NULL);
119  time_t max_age_hdr = 0;
120  time_t expires_hdr = 0;
121  ASN1_TIME *notAfter = NULL;
122  time_t cert_expires = 0;
123  time_t config_expires = 0;
124  time_t expires = 0;
125  int rc = 0;
126 
127  config_expires = current_time + cfg->vcfg_common.max_cache_entry_age;
128 
129  if (!ast_strlen_zero(cache_control_header)) {
130  char *str_max_age;
131 
132  str_max_age = strstr(cache_control_header, "s-maxage");
133  if (!str_max_age) {
134  str_max_age = strstr(cache_control_header, "max-age");
135  }
136 
137  if (str_max_age) {
138  unsigned int m;
139  char *equal = strchr(str_max_age, '=');
140  if (equal && !ast_str_to_uint(equal + 1, &m)) {
141  max_age_hdr = current_time + m;
142  }
143  }
144  }
145 
146  if (!ast_strlen_zero(expires_header)) {
147  struct ast_tm expires_time;
148 
149  ast_strptime(expires_header, "%a, %d %b %Y %T %z", &expires_time);
150  expires_time.tm_isdst = -1;
151  expires_hdr = ast_mktime(&expires_time, "GMT").tv_sec;
152  }
153 
154  notAfter = X509_get_notAfter(cert->xcert);
155  cert_expires = crypto_asn_time_as_time_t(notAfter);
156 
157  /*
158  * ATIS-1000074 says:
159  * The STI-VS shall implement the cache behavior described in
160  * [Ref 10]. If the HTTP response does not include any recognized
161  * caching directives or indicates caching for less than 24 hours,
162  * then the STI-VS should cache the HTTP response for 24 hours.
163  *
164  * Basically, they're saying "cache for 24 hours unless the HTTP
165  * response says to cache for longer." Instead of the fixed 24
166  * hour minumum, however, we'll use max_cache_entry_age instead.
167  *
168  * We got all the possible values of expires so let's find the
169  * highest value greater than the configured max_cache_entry_age.
170  */
171 
172  /* The default */
173  expires = config_expires;
174 
175  if (max_age_hdr > expires) {
176  expires = max_age_hdr;
177  }
178 
179  if (expires_hdr > expires) {
180  expires = expires_hdr;
181  }
182 
183  /*
184  * However... Don't cache for longer than the
185  * certificate is actually valid.
186  */
187  if (cert_expires && cert_expires < expires) {
188  expires = cert_expires;
189  }
190 
191  snprintf(time_buf, sizeof(time_buf), "%ld", expires);
192 
193  rc = ast_db_put(cert->hash_family, "expiration", time_buf);
194  if (rc == 0) {
195  strcpy(cert->expiration, time_buf); /* safe */
196  }
197 
198  return rc;
199 }
200 
201 static int add_cert_key_to_astdb(struct ast_stir_shaken_vs_ctx *cert,
202  const char *cache_control_hdr, const char *expires_hdr)
203 {
204  int rc = 0;
205 
206  rc = ast_db_put(cert->url_family, cert->public_url, cert->hash);
207  if (rc) {
208  return rc;
209  }
210  rc = ast_db_put(cert->hash_family, "path", cert->filename);
211  if (rc) {
212  ast_db_del(cert->url_family, cert->public_url);
213  return rc;
214  }
215 
216  rc = add_cert_expiration_to_astdb(cert, cache_control_hdr, expires_hdr);
217  if (rc) {
218  ast_db_del(cert->url_family, cert->public_url);
219  ast_db_del(cert->hash_family, "path");
220  }
221 
222  return rc;
223 }
224 
225 static int is_cert_cache_entry_expired(char *expiration)
226 {
227  struct timeval current_time = ast_tvnow();
228  struct timeval expires = { .tv_sec = 0, .tv_usec = 0 };
229  int res = 0;
230  SCOPE_ENTER(3, "Checking for cache expiration: %s\n", expiration);
231 
232  if (ast_strlen_zero(expiration)) {
233  SCOPE_EXIT_RTN_VALUE(1, "No expiration date provided\n");
234  }
235 
236  if (ast_str_to_ulong(expiration, (unsigned long *)&expires.tv_sec)) {
237  SCOPE_EXIT_RTN_VALUE(1, "Couldn't convert expiration string '%s' to ulong",
238  expiration);
239  }
240  ast_trace(2, "Expiration comparison: exp: %" PRIu64 " curr: %" PRIu64 " Diff: %" PRIu64 ".\n",
241  expires.tv_sec, current_time.tv_sec, expires.tv_sec - current_time.tv_sec);
242 
243  res = (ast_tvcmp(current_time, expires) == -1 ? 0 : 1);
244  SCOPE_EXIT_RTN_VALUE(res , "entry was %sexpired\n", res ? "" : "not ");
245 }
246 
247 #define ASN1_TAG_TNAUTH_SPC 0
248 #define ASN1_TAG_TNAUTH_TN_RANGE 1
249 #define ASN1_TAG_TNAUTH_TN 2
250 
251 #define IS_GET_OBJ_ERR(ret) (ret & 0x80)
252 
253 static enum ast_stir_shaken_vs_response_code
254  check_tn_auth_list(struct ast_stir_shaken_vs_ctx * ctx)
255 {
256  ASN1_OCTET_STRING *tn_exten;
257  const unsigned char* octet_str_data = NULL;
258  long xlen;
259  int tag, xclass;
260  int ret;
261  SCOPE_ENTER(3, "%s: Checking TNAuthList in cert '%s'\n", ctx->tag, ctx->public_url);
262 
263  tn_exten = crypto_get_cert_extension_data(ctx->xcert, get_tn_auth_nid(), NULL);
264  if (!tn_exten) {
265  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_NO_TN_AUTH_EXT,
266  LOG_ERROR, "%s: Cert '%s' doesn't have a TNAuthList extension\n",
267  ctx->tag, ctx->public_url);
268  }
269  octet_str_data = tn_exten->data;
270 
271  /* The first call to ASN1_get_object should return a SEQUENCE */
272  ret = ASN1_get_object(&octet_str_data, &xlen, &tag, &xclass, tn_exten->length);
273  if (IS_GET_OBJ_ERR(ret)) {
274  crypto_log_openssl(LOG_ERROR, "%s: Cert '%s' has malformed TNAuthList extension\n",
275  ctx->tag, ctx->public_url);
276  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_NO_TN_AUTH_EXT);
277  }
278 
279  if (ret != V_ASN1_CONSTRUCTED || tag != V_ASN1_SEQUENCE) {
280  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_NO_TN_AUTH_EXT,
281  LOG_ERROR, "%s: Cert '%s' has malformed TNAuthList extension (tag %d != V_ASN1_SEQUENCE)\n",
282  ctx->tag, ctx->public_url, tag);
283  }
284 
285  /*
286  * The second call to ASN1_get_object should return one of
287  * the following tags defined in RFC8226 section 9:
288  *
289  * ASN1_TAG_TNAUTH_SPC 0
290  * ASN1_TAG_TNAUTH_TN_RANGE 1
291  * ASN1_TAG_TNAUTH_TN 2
292  *
293  * ATIS-1000080 however limits this to only ASN1_TAG_TNAUTH_SPC
294  *
295  */
296  ret = ASN1_get_object(&octet_str_data, &xlen, &tag, &xclass, tn_exten->length);
297  if (IS_GET_OBJ_ERR(ret)) {
298  crypto_log_openssl(LOG_ERROR, "%s: Cert '%s' has malformed TNAuthList extension\n",
299  ctx->tag, ctx->public_url);
300  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_NO_TN_AUTH_EXT);
301  }
302 
303  if (ret != V_ASN1_CONSTRUCTED || tag != ASN1_TAG_TNAUTH_SPC) {
304  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_NO_SPC_IN_TN_AUTH_EXT,
305  LOG_ERROR, "%s: Cert '%s' has malformed TNAuthList extension (tag %d != ASN1_TAG_TNAUTH_SPC(0))\n",
306  ctx->tag, ctx->public_url, tag);
307  }
308 
309  /* The third call to ASN1_get_object should contain the SPC */
310  ret = ASN1_get_object(&octet_str_data, &xlen, &tag, &xclass, tn_exten->length);
311  if (ret != 0) {
312  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_NO_SPC_IN_TN_AUTH_EXT,
313  LOG_ERROR, "%s: Cert '%s' has malformed TNAuthList extension (no SPC)\n",
314  ctx->tag, ctx->public_url);
315  }
316 
317  if (ast_string_field_set(ctx, cert_spc, (char *)octet_str_data) != 0) {
318  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR);
319  }
320 
321  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_SUCCESS, "%s: Cert '%s' with SPC: %s CN: %s has valid TNAuthList\n",
322  ctx->tag, ctx->public_url, ctx->cert_spc, ctx->cert_cn);
323 }
324 #undef IS_GET_OBJ_ERR
325 
326 static enum ast_stir_shaken_vs_response_code check_cert(
327  struct ast_stir_shaken_vs_ctx * ctx)
328 {
329  RAII_VAR(char *, CN, NULL, ast_free);
330  int res = 0;
331  const char *err_msg;
332  SCOPE_ENTER(3, "%s: Validating cert '%s'\n", ctx->tag, ctx->public_url);
333 
334  CN = crypto_get_cert_subject(ctx->xcert, "CN");
335  if (!CN) {
336  CN = crypto_get_cert_subject(ctx->xcert, NULL);
337  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_CONTENTS_INVALID,
338  LOG_ERROR, "%s: Cert '%s' has no commonName(CN) in Subject '%s'\n",
339  ctx->tag, ctx->public_url, CN);
340  }
341 
342  res = ast_string_field_set(ctx, cert_cn, CN);
343  if (res != 0) {
344  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR);
345  }
346 
347  ast_trace(3,"%s: Checking ctx against CA ctx\n", ctx->tag);
348  res = crypto_is_cert_trusted(ctx->eprofile->vcfg_common.tcs, ctx->xcert, &err_msg);
349  if (!res) {
350  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_NOT_TRUSTED,
351  LOG_ERROR, "%s: Cert '%s' not trusted: %s\n",
352  ctx->tag, ctx->public_url, err_msg);
353  }
354 
355  ast_trace(3,"%s: Attempting to get the raw pubkey\n", ctx->tag);
356  ctx->raw_key_len = crypto_get_raw_pubkey_from_cert(ctx->xcert,
357  &ctx->raw_key);
358  if (ctx->raw_key_len <= 0) {
359  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_NO_RAW_KEY,
360  LOG_ERROR, "%s: Unable to extract raw public key from '%s'\n",
361  ctx->tag, ctx->public_url);
362  }
363 
364  ast_trace(3,"%s: Checking cert '%s' validity dates\n",
365  ctx->tag, ctx->public_url);
366  if (!crypto_is_cert_time_valid(ctx->xcert, ctx->validity_check_time)) {
367  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_DATE_INVALID,
368  LOG_ERROR, "%s: Cert '%s' dates not valid\n",
369  ctx->tag, ctx->public_url);
370  }
371 
372  SCOPE_EXIT_RTN_VALUE(check_tn_auth_list(ctx),
373  "%s: Cert '%s' with SPC: %s CN: %s is valid\n",
374  ctx->tag, ctx->public_url, ctx->cert_spc, ctx->cert_cn);
375 }
376 
377 static enum ast_stir_shaken_vs_response_code retrieve_cert_from_url(
378  struct ast_stir_shaken_vs_ctx *ctx)
379 {
380  FILE *cert_file;
381  long http_code;
382  int rc = 0;
383  enum ast_stir_shaken_vs_response_code vs_rc;
385  ast_calloc(1, sizeof(*header_data)), curl_header_data_free);
386  RAII_VAR(struct curl_write_data *, write_data,
387  ast_calloc(1, sizeof(*write_data)), curl_write_data_free);
388  RAII_VAR(struct curl_open_socket_data *, open_socket_data,
389  ast_calloc(1, sizeof(*open_socket_data)), curl_open_socket_data_free);
390 
391  const char *cache_control;
392  const char *expires;
393  SCOPE_ENTER(2, "%s: Attempting to retrieve '%s' from net\n",
394  ctx->tag, ctx->public_url);
395 
396  if (!header_data || !write_data || !open_socket_data) {
397  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR,
398  LOG_ERROR, "%s: Unable to allocate memory for curl '%s' transaction\n",
399  ctx->tag, ctx->public_url);
400  }
401 
402  header_data->debug_info = ast_strdup(ctx->public_url);
403  write_data->debug_info = ast_strdup(ctx->public_url);
404  write_data->max_download_bytes = 8192;
405  write_data->stream_buffer = NULL;
406  open_socket_data->debug_info = ast_strdup(ctx->public_url);
407  open_socket_data->acl = ctx->eprofile->vcfg_common.acl;
408 
409  if (!header_data->debug_info || !write_data->debug_info ||
410  !open_socket_data->debug_info) {
411  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR,
412  LOG_ERROR, "%s: Unable to allocate memory for curl '%s' transaction\n",
413  ctx->tag, ctx->public_url);
414  }
415 
416  http_code = curler(ctx->public_url,
417  ctx->eprofile->vcfg_common.curl_timeout,
418  write_data, header_data, open_socket_data);
419 
420  if (http_code / 100 != 2) {
421  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_RETRIEVAL_FAILURE,
422  LOG_ERROR, "%s: Failed to retrieve cert %s: code %ld\n",
423  ctx->tag, ctx->public_url, http_code);
424  }
425 
426  if (!ast_begins_with(write_data->stream_buffer, BEGIN_CERTIFICATE_STR)) {
427  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_CONTENTS_INVALID,
428  LOG_ERROR, "%s: Cert '%s' contains invalid data\n",
429  ctx->tag, ctx->public_url);
430  }
431 
432  ctx->xcert = crypto_load_cert_from_memory(write_data->stream_buffer,
433  write_data->stream_bytes_downloaded);
434  if (!ctx->xcert) {
435  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_CONTENTS_INVALID,
436  LOG_ERROR, "%s: Cert '%s' was not parseable as an X509 certificate\n",
437  ctx->tag, ctx->public_url);
438  }
439 
440  vs_rc = check_cert(ctx);
441  if (vs_rc != AST_STIR_SHAKEN_VS_SUCCESS) {
442  X509_free(ctx->xcert);
443  ctx->xcert = NULL;
444  SCOPE_EXIT_RTN_VALUE(vs_rc, "%s: Cert '%s' failed validity checks\n",
445  ctx->tag, ctx->public_url);
446  }
447 
448  cert_file = fopen(ctx->filename, "w");
449  if (!cert_file) {
450  X509_free(ctx->xcert);
451  ctx->xcert = NULL;
452  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR,
453  LOG_ERROR, "%s: Failed to write cert %s: file '%s' %s (%d)\n",
454  ctx->tag, ctx->public_url, ctx->filename, strerror(errno), errno);
455  }
456 
457  rc = fputs(write_data->stream_buffer, cert_file);
458  fclose(cert_file);
459  if (rc == EOF) {
460  X509_free(ctx->xcert);
461  ctx->xcert = NULL;
462  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR,
463  LOG_ERROR, "%s: Failed to write cert %s: file '%s' %s (%d)\n",
464  ctx->tag, ctx->public_url, ctx->filename, strerror(errno), errno);
465  }
466 
467  ast_trace(2, "%s: Cert '%s' written to file '%s'\n",
468  ctx->tag, ctx->public_url, ctx->filename);
469 
470  ast_trace(2, "%s: Adding cert '%s' to astdb",
471  ctx->tag, ctx->public_url);
472  cache_control = ast_variable_find_in_list(header_data->headers, "cache-control");
473  expires = ast_variable_find_in_list(header_data->headers, "expires");
474 
475  rc = add_cert_key_to_astdb(ctx, cache_control, expires);
476  if (rc) {
477  X509_free(ctx->xcert);
478  ctx->xcert = NULL;
479  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR,
480  LOG_ERROR, "%s: Unable to add cert '%s' to ASTDB\n",
481  ctx->tag, ctx->public_url);
482  }
483 
484  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_SUCCESS,
485  "%s: Cert '%s' successfully retrieved from internet and cached\n",
486  ctx->tag, ctx->public_url);
487 }
488 
489 static enum ast_stir_shaken_vs_response_code
490  retrieve_cert_from_cache(struct ast_stir_shaken_vs_ctx *ctx)
491 {
492  int rc = 0;
493  enum ast_stir_shaken_vs_response_code vs_rc;
494 
495  SCOPE_ENTER(2, "%s: Attempting to retrieve cert '%s' from cache\n",
496  ctx->tag, ctx->public_url);
497 
498  if (!ast_db_exists(ctx->hash_family, "path")) {
499  cleanup_cert_from_astdb_and_fs(ctx);
500  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_CACHE_MISS,
501  "%s: No cert found in astdb for '%s'\n",
502  ctx->tag, ctx->public_url);
503  }
504 
505  rc = ast_db_get(ctx->hash_family, "expiration", ctx->expiration, sizeof(ctx->expiration));
506  if (rc) {
507  cleanup_cert_from_astdb_and_fs(ctx);
508  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_CACHE_MISS,
509  "%s: No cert found in astdb for '%s'\n",
510  ctx->tag, ctx->public_url);
511  }
512 
513  if (!ast_file_is_readable(ctx->filename)) {
514  cleanup_cert_from_astdb_and_fs(ctx);
515  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_CACHE_MISS,
516  "%s: Cert file '%s' was not found or was not readable for '%s'\n",
517  ctx->tag, ctx->filename, ctx->public_url);
518  }
519 
520  if (is_cert_cache_entry_expired(ctx->expiration)) {
521  cleanup_cert_from_astdb_and_fs(ctx);
522  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_CACHE_EXPIRED,
523  "%s: Cert file '%s' cache entry was expired for '%s'\n",
524  ctx->tag, ctx->filename, ctx->public_url);
525  }
526 
527  ctx->xcert = crypto_load_cert_from_file(ctx->filename);
528  if (!ctx->xcert) {
529  cleanup_cert_from_astdb_and_fs(ctx);
530  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_CERT_CONTENTS_INVALID,
531  "%s: Cert file '%s' was not parseable as an X509 certificate for '%s'\n",
532  ctx->tag, ctx->filename, ctx->public_url);
533  }
534 
535  vs_rc = check_cert(ctx);
536  if (vs_rc != AST_STIR_SHAKEN_VS_SUCCESS) {
537  X509_free(ctx->xcert);
538  ctx->xcert = NULL;
539  SCOPE_EXIT_RTN_VALUE(vs_rc, "%s: Cert '%s' failed validity checks\n",
540  ctx->tag, ctx->public_url);
541  }
542 
543  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_SUCCESS,
544  "%s: Cert '%s' successfully retrieved from cache\n",
545  ctx->tag, ctx->public_url);
546 }
547 
548 static enum ast_stir_shaken_vs_response_code ctx_populate(
549  struct ast_stir_shaken_vs_ctx *ctx)
550 {
551  char hash[41];
552 
553  ast_sha1_hash(hash, ctx->public_url);
554  if (ast_string_field_set(ctx, hash, hash) != 0) {
555  return AST_STIR_SHAKEN_VS_INTERNAL_ERROR;
556  }
557 
558  if (ast_string_field_build(ctx, filename, "%s/%s.pem",
559  ctx->eprofile->vcfg_common.cert_cache_dir, hash) != 0) {
560  return AST_STIR_SHAKEN_VS_INTERNAL_ERROR;
561  }
562 
563  if (ast_string_field_build(ctx, hash_family, "%s/hash/%s",
564  AST_DB_FAMILY, hash) != 0) {
565  return AST_STIR_SHAKEN_VS_INTERNAL_ERROR;
566  }
567 
568  if (ast_string_field_build(ctx, url_family, "%s/url", AST_DB_FAMILY) != 0) {
569  return AST_STIR_SHAKEN_VS_INTERNAL_ERROR;
570  }
571 
572  return AST_STIR_SHAKEN_VS_SUCCESS;
573 }
574 
575 static enum ast_stir_shaken_vs_response_code
576  retrieve_verification_cert(struct ast_stir_shaken_vs_ctx *ctx)
577 {
578  enum ast_stir_shaken_vs_response_code rc = AST_STIR_SHAKEN_VS_SUCCESS;
579  SCOPE_ENTER(3, "%s: Retrieving cert '%s'\n", ctx->tag, ctx->public_url);
580 
581  ast_trace(1, "%s: Checking cache for cert '%s'\n", ctx->tag, ctx->public_url);
582  rc = retrieve_cert_from_cache(ctx);
583  if (rc == AST_STIR_SHAKEN_VS_SUCCESS) {
584  SCOPE_EXIT_RTN_VALUE(rc, "%s: Using cert '%s' from cache\n",
585  ctx->tag, ctx->public_url);;
586  }
587 
588  ast_trace(1, "%s: No valid cert for '%s' available in cache\n",
589  ctx->tag, ctx->public_url);
590  ast_trace(1, "%s: Retrieving cert directly from url '%s'\n",
591  ctx->tag, ctx->public_url);
592 
593  rc = retrieve_cert_from_url(ctx);
594  if (rc == AST_STIR_SHAKEN_VS_SUCCESS) {
595  SCOPE_EXIT_RTN_VALUE(rc, "%s: Using cert '%s' from internet\n",
596  ctx->tag, ctx->public_url);
597  }
598 
599  SCOPE_EXIT_LOG_RTN_VALUE(rc, LOG_ERROR,
600  "%s: Unable to retrieve cert '%s' from cache or internet\n",
601  ctx->tag, ctx->public_url);
602 }
603 
604 enum ast_stir_shaken_vs_response_code
605  ast_stir_shaken_vs_ctx_add_identity_hdr(
606  struct ast_stir_shaken_vs_ctx * ctx, const char *identity_hdr)
607 {
608  return ast_string_field_set(ctx, identity_hdr, identity_hdr) == 0 ?
609  AST_STIR_SHAKEN_VS_SUCCESS : AST_STIR_SHAKEN_VS_INTERNAL_ERROR;
610 }
611 
612 enum ast_stir_shaken_vs_response_code
613  ast_stir_shaken_vs_ctx_add_date_hdr(struct ast_stir_shaken_vs_ctx * ctx,
614  const char *date_hdr)
615 {
616  return ast_string_field_set(ctx, date_hdr, date_hdr) == 0 ?
617  AST_STIR_SHAKEN_VS_SUCCESS : AST_STIR_SHAKEN_VS_INTERNAL_ERROR;
618 }
619 
620 enum stir_shaken_failure_action_enum
621  ast_stir_shaken_vs_get_failure_action(
622  struct ast_stir_shaken_vs_ctx *ctx)
623 {
624  return ctx->eprofile->vcfg_common.stir_shaken_failure_action;
625 }
626 
627 int ast_stir_shaken_vs_get_use_rfc9410_responses(
628  struct ast_stir_shaken_vs_ctx *ctx)
629 {
630  return ctx->eprofile->vcfg_common.use_rfc9410_responses;
631 }
632 
633 void ast_stir_shaken_vs_ctx_set_response_code(
634  struct ast_stir_shaken_vs_ctx *ctx,
635  enum ast_stir_shaken_vs_response_code vs_rc)
636 {
637  ctx->failure_reason = vs_rc;
638 }
639 
640 static void ctx_destructor(void *obj)
641 {
642  struct ast_stir_shaken_vs_ctx *ctx = obj;
643 
644  ao2_cleanup(ctx->eprofile);
645  ast_free(ctx->raw_key);
647  X509_free(ctx->xcert);
648 }
649 
650 enum ast_stir_shaken_vs_response_code
651  ast_stir_shaken_vs_ctx_create(const char *caller_id,
652  struct ast_channel *chan, const char *profile_name,
653  const char *tag, struct ast_stir_shaken_vs_ctx **ctxout)
654 {
655  RAII_VAR(struct ast_stir_shaken_vs_ctx *, ctx, NULL, ao2_cleanup);
656  RAII_VAR(struct profile_cfg *, profile, NULL, ao2_cleanup);
657  RAII_VAR(struct verification_cfg *, vs, NULL, ao2_cleanup);
658  RAII_VAR(char *, canon_caller_id , canonicalize_tn_alloc(caller_id), ast_free);
659 
660  const char *t = S_OR(tag, S_COR(chan, ast_channel_name(chan), ""));
661  SCOPE_ENTER(3, "%s: Enter\n", t);
662 
663  if (ast_strlen_zero(tag)) {
664  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_ARGUMENTS,
665  LOG_ERROR, "%s: Must provide tag\n", t);
666  }
667 
668  if (ast_strlen_zero(canon_caller_id)) {
669  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_ARGUMENTS,
670  LOG_ERROR, "%s: Must provide caller_id\n", t);
671  }
672 
673  if (ast_strlen_zero(profile_name)) {
674  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_DISABLED,
675  "%s: Disabled due to missing profile name\n", t);
676  }
677 
678  vs = vs_get_cfg();
679  if (vs->global_disable) {
680  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_DISABLED,
681  "%s: Globally disabled\n", t);
682  }
683 
684  profile = eprofile_get_cfg(profile_name);
685  if (!profile) {
686  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_DISABLED,
687  LOG_ERROR, "%s: No profile for profile name '%s'. Call will continue\n", tag,
688  profile_name);
689  }
690 
691  if (!PROFILE_ALLOW_VERIFY(profile)) {
692  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_DISABLED,
693  "%s: Disabled by profile\n", t);
694  }
695 
696  ctx = ao2_alloc_options(sizeof(*ctx), ctx_destructor,
698  if (!ctx) {
699  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR);
700  }
701  if (ast_string_field_init(ctx, 1024) != 0) {
702  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR);
703  }
704 
705  if (ast_string_field_set(ctx, tag, tag) != 0) {
706  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR);
707  }
708 
709  ctx->chan = chan;
710  if (ast_string_field_set(ctx, caller_id, canon_caller_id) != 0) {
711  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR);
712  }
713 
714  /* Transfer references to ctx */
715  ctx->eprofile = profile;
716  profile = NULL;
717 
718  ao2_ref(ctx, +1);
719  *ctxout = ctx;
720  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_SUCCESS, "%s: Done\n", t);
721 }
722 
723 static enum ast_stir_shaken_vs_response_code check_date_header(
724  struct ast_stir_shaken_vs_ctx * ctx)
725 {
726  struct ast_tm date_hdr_tm;
727  struct timeval date_hdr_timeval;
728  struct timeval current_timeval;
729  char *remainder;
730  char timezone[80] = { 0 };
731  int64_t time_diff;
732  SCOPE_ENTER(3, "%s: Checking date header: '%s'\n",
733  ctx->tag, ctx->date_hdr);
734 
735  if (!(remainder = ast_strptime(ctx->date_hdr, "%a, %d %b %Y %T", &date_hdr_tm))) {
736  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_DATE_HDR_PARSE_FAILURE,
737  LOG_ERROR, "%s: Failed to parse: '%s'\n",
738  ctx->tag, ctx->date_hdr);
739  }
740 
741  sscanf(remainder, "%79s", timezone);
742 
743  if (ast_strlen_zero(timezone)) {
744  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_DATE_HDR_PARSE_FAILURE,
745  LOG_ERROR, "%s: A timezone is required: '%s'\n",
746  ctx->tag, ctx->date_hdr);
747  }
748 
749  date_hdr_timeval = ast_mktime(&date_hdr_tm, timezone);
750  ctx->date_hdr_time = date_hdr_timeval.tv_sec;
751  current_timeval = ast_tvnow();
752 
753  time_diff = ast_tvdiff_ms(current_timeval, date_hdr_timeval);
754  ast_trace(3, "%zu %zu %zu %d\n", current_timeval.tv_sec,
755  date_hdr_timeval.tv_sec,
756  (current_timeval.tv_sec - date_hdr_timeval.tv_sec), (int)time_diff);
757  if (time_diff < 0) {
758  /* An INVITE from the future! */
759  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_DATE_HDR_EXPIRED,
760  LOG_ERROR, "%s: Future date: '%s'\n",
761  ctx->tag, ctx->date_hdr);
762  } else if (time_diff > (ctx->eprofile->vcfg_common.max_date_header_age * 1000)) {
763  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_DATE_HDR_EXPIRED,
764  LOG_ERROR, "%s: More than %u seconds old: '%s'\n",
765  ctx->tag, ctx->eprofile->vcfg_common.max_date_header_age, ctx->date_hdr);
766  }
767 
768  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_SUCCESS,
769  "%s: Success: '%s'\n", ctx->tag, ctx->date_hdr);
770 }
771 
772 #define FULL_URL_REGEX "^([a-zA-Z]+)://(([^@]+@[^:]+):)?(([^:/?]+)|([0-9.]+)|([[][0-9a-fA-F:]+[]]))(:([0-9]+))?(/([^#\\?]+))?(\\?([^#]+))?(#(.*))?"
773 #define FULL_URL_REGEX_GROUPS 15
774 /*
775  * Broken down...
776  * ^([a-zA-Z]+) must start with scheme group 1
777  * ://
778  * (([^@]+@[^:]+):)? optional user@pass group 3
779  * ( start hostname group group 4
780  * ([^:/?]+) normal fqdn group 5
781  * |([0-9.]+) OR IPv4 address group 6
782  * |([[][0-9a-fA-F:]+[]]) OR IPv6 address group 7
783  * ) end hostname group
784  * (:([0-9]+))? optional port group 9
785  * (/([^#\?]+))? optional path group 11
786  * (\?([^#]+))? optional query string group 13
787  * (#([^?]+))? optional fagment group 15
788  *
789  * If you change the regex, make sure FULL_URL_REGEX_GROUPS is updated.
790  */
791 #define URL_MATCH_SCHEME 1
792 #define URL_MATCH_USERPASS 3
793 #define URL_MATCH_HOST 4
794 #define URL_MATCH_PORT 9
795 #define URL_MATCH_PATH 11
796 #define URL_MATCH_QUERY 13
797 #define URL_MATCH_FRAGMENT 15
798 
799 #define get_match_string(__x5u, __pmatch, __i) \
800 ({ \
801  char *__match = NULL; \
802  if (__pmatch[__i].rm_so >= 0) { \
803  regoff_t __len = __pmatch[__i].rm_eo - __pmatch[__i].rm_so; \
804  const char *__start = __x5u + __pmatch[__i].rm_so; \
805  __match = ast_alloca(__len + 1); \
806  ast_copy_string(__match, __start, __len + 1); \
807  } \
808  __match; \
809 })
810 
811 #define DUMP_X5U_MATCH() \
812 {\
813  int i; \
814  if (TRACE_ATLEAST(4)) { \
815  ast_trace(-1, "%s: x5u: %s\n", ctx->tag, x5u); \
816  for (i=0;i<FULL_URL_REGEX_GROUPS;i++) { \
817  const char *m = get_match_string(x5u, pmatch, i); \
818  if (m) { \
819  ast_trace(-1, "%s: %2d %s\n", ctx->tag, i, m); \
820  } \
821  } \
822  } \
823 }
824 
825 static int check_x5u_url(struct ast_stir_shaken_vs_ctx * ctx,
826  const char *x5u)
827 {
828  int max_groups = url_match_regex.re_nsub + 1;
829  regmatch_t pmatch[max_groups];
830  int rc;
831  SCOPE_ENTER(3, "%s: Checking x5u '%s'\n", ctx->tag, x5u);
832 
833  rc = regexec(&url_match_regex, x5u, max_groups, pmatch, 0);
834  if (rc) {
835  char regex_error[512];
836  regerror(rc, &url_match_regex, regex_error, sizeof(regex_error));
837  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_X5U, LOG_ERROR,
838  "%s: x5u '%s' in Identity header failed basic URL validation: %s\n",
839  ctx->tag, x5u, regex_error);
840  }
841 
842  if (ctx->eprofile->vcfg_common.relax_x5u_port_scheme_restrictions
843  != relax_x5u_port_scheme_restrictions_YES) {
844  const char *scheme = get_match_string(x5u, pmatch, URL_MATCH_SCHEME);
845  const char *port = get_match_string(x5u, pmatch, URL_MATCH_PORT);
846 
847  if (!ast_strings_equal(scheme, "https")) {
848  DUMP_X5U_MATCH();
849  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_X5U, LOG_ERROR,
850  "%s: x5u '%s': scheme '%s' not https\n",
851  ctx->tag, x5u, scheme);
852  }
853  if (!ast_strlen_zero(port)) {
854  if (!ast_strings_equal(port, "443")
855  || !ast_strings_equal(port, "8443")) {
856  DUMP_X5U_MATCH();
857  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_X5U, LOG_ERROR,
858  "%s: x5u '%s': port '%s' not port 443 or 8443\n",
859  ctx->tag, x5u, port);
860  }
861  }
862  }
863 
864  if (ctx->eprofile->vcfg_common.relax_x5u_path_restrictions
865  != relax_x5u_path_restrictions_YES) {
866  const char *userpass = get_match_string(x5u, pmatch, URL_MATCH_USERPASS);
867  const char *qs = get_match_string(x5u, pmatch, URL_MATCH_QUERY);
868  const char *frag = get_match_string(x5u, pmatch, URL_MATCH_FRAGMENT);
869 
870  if (!ast_strlen_zero(userpass) || !ast_strlen_zero(qs)
871  || !ast_strlen_zero(frag)) {
872  DUMP_X5U_MATCH();
873  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_X5U, LOG_ERROR,
874  "%s: x5u '%s' contains user:password, query parameters or fragment\n",
875  ctx->tag, x5u);
876  }
877  }
878 
879  return 0;
880 }
881 
882 enum ast_stir_shaken_vs_response_code
883  ast_stir_shaken_vs_verify(struct ast_stir_shaken_vs_ctx * ctx)
884 {
885  RAII_VAR(char *, jwt_encoded, NULL, ast_free);
886  RAII_VAR(jwt_t *, jwt, NULL, jwt_free);
887  RAII_VAR(struct ast_json *, grants, NULL, ast_json_unref);
888  char *p = NULL;
889  char *grants_str = NULL;
890  const char *x5u;
891  const char *ppt_header = NULL;
892  const char *grant = NULL;
893  time_t now_s = time(NULL);
894  time_t iat;
895  struct ast_json *grant_obj = NULL;
896  int len;
897  int rc;
898  enum ast_stir_shaken_vs_response_code vs_rc;
899  SCOPE_ENTER(3, "%s: Verifying\n", ctx ? ctx->tag : "NULL");
900 
901  if (!ctx) {
902  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR, LOG_ERROR,
903  "%s: No context object!\n", "NULL");
904  }
905 
906  if (ast_strlen_zero(ctx->identity_hdr)) {
907  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR, LOG_ERROR,
908  "%s: No identity header in ctx\n", ctx->tag);
909  }
910 
911  p = strchr(ctx->identity_hdr, ';');
912  len = p - ctx->identity_hdr + 1;
913  jwt_encoded = ast_malloc(len);
914  if (!jwt_encoded) {
915  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR, LOG_ERROR,
916  "%s: Failed to allocate memory for encoded jwt\n", ctx->tag);
917  }
918 
919  memcpy(jwt_encoded, ctx->identity_hdr, len);
920  jwt_encoded[len - 1] = '\0';
921 
922  jwt_decode(&jwt, jwt_encoded, NULL, 0);
923 
924  ppt_header = jwt_get_header(jwt, "ppt");
925  if (!ppt_header || strcmp(ppt_header, STIR_SHAKEN_PPT)) {
926  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_PPT, "%s: %s\n",
927  ctx->tag, vs_response_code_to_str(AST_STIR_SHAKEN_VS_INVALID_OR_NO_PPT));
928  }
929 
930  vs_rc = check_date_header(ctx);
931  if (vs_rc != AST_STIR_SHAKEN_VS_SUCCESS) {
932  SCOPE_EXIT_LOG_RTN_VALUE(vs_rc, LOG_ERROR,
933  "%s: Date header verification failed\n", ctx->tag);
934  }
935 
936  x5u = jwt_get_header(jwt, "x5u");
937  if (ast_strlen_zero(x5u)) {
938  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_X5U, LOG_ERROR,
939  "%s: No x5u in Identity header\n", ctx->tag);
940  }
941 
942  rc = check_x5u_url(ctx, x5u);
943  if (rc != AST_STIR_SHAKEN_VS_SUCCESS) {
944  SCOPE_EXIT_RTN_VALUE(vs_rc,
945  "%s: x5u URL verification failed\n", ctx->tag);
946  }
947 
948  ast_trace(3, "%s: Decoded enough to get x5u: '%s'\n", ctx->tag, x5u);
949  if (ast_string_field_set(ctx, public_url, x5u) != 0) {
950  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR, LOG_ERROR,
951  "%s: Failed to set public_url '%s'\n", ctx->tag, x5u);
952  }
953 
954  iat = jwt_get_grant_int(jwt, "iat");
955  if (iat == 0) {
956  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_NO_IAT, LOG_ERROR,
957  "%s: No 'iat' in Identity header\n", ctx->tag);
958  }
959  ast_trace(1, "date_hdr: %zu iat: %zu diff: %zu\n",
960  ctx->date_hdr_time, iat, ctx->date_hdr_time - iat);
961  if (iat + ctx->eprofile->vcfg_common.max_iat_age < now_s) {
962  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_IAT_EXPIRED,
963  "%s: iat %ld older than %u seconds\n", ctx->tag,
964  iat, ctx->eprofile->vcfg_common.max_iat_age);
965  }
966  ctx->validity_check_time = iat;
967 
968  vs_rc = ctx_populate(ctx);
969  if (vs_rc != AST_STIR_SHAKEN_VS_SUCCESS) {
970  SCOPE_EXIT_LOG_RTN_VALUE(vs_rc, LOG_ERROR,
971  "%s: Unable to populate ctx\n", ctx->tag);
972  }
973 
974  vs_rc = retrieve_verification_cert(ctx);
975  if (vs_rc != AST_STIR_SHAKEN_VS_SUCCESS) {
976  SCOPE_EXIT_LOG_RTN_VALUE(vs_rc, LOG_ERROR,
977  "%s: Could not get valid cert from '%s'\n", ctx->tag, ctx->public_url);
978  }
979 
980  jwt_free(jwt);
981  jwt = NULL;
982 
983  rc = jwt_decode(&jwt, jwt_encoded, ctx->raw_key, ctx->raw_key_len);
984  if (rc != 0) {
985  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_SIGNATURE_VALIDATION,
986  LOG_ERROR, "%s: Signature validation failed for '%s'\n",
987  ctx->tag, ctx->public_url);
988  }
989 
990  ast_trace(1, "%s: Decoding succeeded\n", ctx->tag);
991 
992  ppt_header = jwt_get_header(jwt, "alg");
993  if (!ppt_header || strcmp(ppt_header, STIR_SHAKEN_ENCRYPTION_ALGORITHM)) {
994  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_ALG,
995  "%s: %s\n", ctx->tag,
996  vs_response_code_to_str(AST_STIR_SHAKEN_VS_INVALID_OR_NO_ALG));
997  }
998 
999  ppt_header = jwt_get_header(jwt, "ppt");
1000  if (!ppt_header || strcmp(ppt_header, STIR_SHAKEN_PPT)) {
1001  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_PPT,
1002  "%s: %s\n", ctx->tag,
1003  vs_response_code_to_str(AST_STIR_SHAKEN_VS_INVALID_OR_NO_PPT));
1004  }
1005 
1006  ppt_header = jwt_get_header(jwt, "typ");
1007  if (!ppt_header || strcmp(ppt_header, STIR_SHAKEN_TYPE)) {
1008  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_TYP,
1009  "%s: %s\n", ctx->tag,
1010  vs_response_code_to_str(AST_STIR_SHAKEN_VS_INVALID_OR_NO_TYP));
1011  }
1012 
1013  grants_str = jwt_get_grants_json(jwt, NULL);
1014  if (ast_strlen_zero(grants_str)) {
1015  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_GRANTS,
1016  "%s: %s\n", ctx->tag,
1017  vs_response_code_to_str(AST_STIR_SHAKEN_VS_INVALID_OR_NO_GRANTS));
1018  }
1019  ast_trace(1, "grants: %s\n", grants_str);
1020  grants = ast_json_load_string(grants_str, NULL);
1021  ast_std_free(grants_str);
1022  if (!grants) {
1023  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_GRANTS,
1024  "%s: %s\n", ctx->tag,
1025  vs_response_code_to_str(AST_STIR_SHAKEN_VS_INVALID_OR_NO_GRANTS));
1026  }
1027 
1028  grant = ast_json_object_string_get(grants, "attest");
1029  if (ast_strlen_zero(grant)) {
1030  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_ATTEST,
1031  "%s: No 'attest' in Identity header\n", ctx->tag);
1032  }
1033  if (grant[0] < 'A' || grant[0] > 'C') {
1034  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_INVALID_OR_NO_ATTEST,
1035  "%s: Invalid attest value '%s'\n", ctx->tag, grant);
1036  }
1037  ast_string_field_set(ctx, attestation, grant);
1038  ast_trace(1, "got attest: %s\n", grant);
1039 
1040  grant_obj = ast_json_object_get(grants, "dest");
1041  if (!grant_obj) {
1042  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_NO_DEST_TN,
1043  "%s: No 'dest' in Identity header\n", ctx->tag);
1044  }
1045  if (TRACE_ATLEAST(3)) {
1046  char *otn = ast_json_dump_string(grant_obj);
1047  ast_trace(1, "got dest: %s\n", otn);
1048  ast_json_free(otn);
1049  }
1050 
1051  grant_obj = ast_json_object_get(grants, "orig");
1052  if (!grant_obj) {
1053  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_NO_ORIG_TN,
1054  "%s: No 'orig' in Identity header\n", ctx->tag);
1055  }
1056  if (TRACE_ATLEAST(3)) {
1057  char *otn = ast_json_dump_string(grant_obj);
1058  ast_trace(1, "got orig: %s\n", otn);
1059  ast_json_free(otn);
1060  }
1061  grant = ast_json_object_string_get(grant_obj, "tn");
1062  if (!grant) {
1063  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_NO_ORIG_TN,
1064  "%s: No 'orig.tn' in Indentity header\n", ctx->tag);
1065  }
1066  ast_string_field_set(ctx, orig_tn, grant);
1067  if (strcmp(ctx->caller_id, ctx->orig_tn) != 0) {
1068  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_CID_ORIG_TN_MISMATCH,
1069  "%s: Mismatched cid '%s' and orig_tn '%s'\n", ctx->tag,
1070  ctx->caller_id, grant);
1071  }
1072 
1073  grant = ast_json_object_string_get(grants, "origid");
1074  if (ast_strlen_zero(grant)) {
1075  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_NO_ORIGID,
1076  "%s: No 'origid' in Identity header\n", ctx->tag);
1077  }
1078 
1079  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_VS_SUCCESS,
1080  "%s: verification succeeded\n", ctx->tag);
1081 }
1082 
1083 int vs_reload()
1084 {
1085  vs_config_reload();
1086 
1087  return 0;
1088 }
1089 
1090 int vs_unload()
1091 {
1092  vs_config_unload();
1093  if (url_match_regex.re_nsub > 0) {
1094  regfree(&url_match_regex);
1095  }
1096 
1097  return 0;
1098 }
1099 
1100 int vs_load()
1101 {
1102  int rc = 0;
1103 
1104  if (vs_config_load()) {
1105  return AST_MODULE_LOAD_DECLINE;
1106  }
1107 
1108  rc = regcomp(&url_match_regex, FULL_URL_REGEX, REG_EXTENDED);
1109  if (rc) {
1110  char regex_error[512];
1111  regerror(rc, &url_match_regex, regex_error, sizeof(regex_error));
1112  ast_log(LOG_ERROR, "Verification service URL regex failed to compile: %s\n", regex_error);
1113  vs_unload();
1114  return AST_MODULE_LOAD_DECLINE;
1115  }
1116  if (url_match_regex.re_nsub != FULL_URL_REGEX_GROUPS) {
1117  ast_log(LOG_ERROR, "The verification service URL regex was updated without updating FULL_URL_REGEX_GROUPS\n");
1118  vs_unload();
1119  return AST_MODULE_LOAD_DECLINE;
1120  }
1121 
1122  return AST_MODULE_LOAD_SUCCESS;
1123 }
#define ARRAY_IN_BOUNDS(v, a)
Checks to see if value is within the bounds of the given array.
Definition: utils.h:687
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
Context structure passed to ast_curl_open_socket_default_cb.
Definition: curl_utils.h:341
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
#define AST_DB_FAMILY
Definition: media_cache.c:42
int ast_file_is_readable(const char *filename)
Test that a file exists and is readable by the effective user.
Definition: utils.c:3107
Provide cryptographic signature routines.
void ast_json_free(void *p)
Asterisk's custom JSON allocator. Exposed for use by unit tests.
Definition: json.c:52
#define ast_json_dump_string(root)
Encode a JSON value to a compact string.
Definition: json.h:810
Data structure used for ast_sip_push_task_wait_serializer.
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
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:567
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:107
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
Utility functions.
Custom localtime functions for multiple timezones.
Configuration File Parser.
Context structure passed to ast_curl_write_default_cb.
Definition: curl_utils.h:245
General Asterisk PBX channel definitions.
Asterisk JSON abstraction layer.
Asterisk file paths, configured in asterisk.conf.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
Access Control of various sorts.
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ast_json_object_string_get(object, key)
Get a string field from a JSON object.
Definition: json.h:600
Context structure passed to ast_curl_header_default_cb.
Definition: curl_utils.h:163
#define S_COR(a, b, c)
returns the equivalent of logic or for strings, with an additional boolean check: second one if not e...
Definition: strings.h:87
Conversion utility functions.
const char * ast_variable_find_in_list(const struct ast_variable *list, const char *variable)
Gets the value of a variable from a variable list by name.
Definition: main/config.c:919
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
int ast_db_exists(const char *family, const char *key)
Check if family/key exitsts.
Definition: main/db.c:444
long curler(const char *url, int request_timeout, struct curl_write_data *write_data, struct curl_header_data *header_data, struct curl_open_socket_data *open_socket_data)
Perform a curl request.
Definition: curl_utils.c:232
char * ast_strptime(const char *s, const char *format, struct ast_tm *tm)
Special version of strptime(3) which places the answer in the common structure ast_tm. Also, unlike strptime(3), ast_strptime() initializes its memory prior to use.
Definition: localtime.c:2550
void ast_sha1_hash(char *output, const char *input)
Produces SHA1 hash based on input string.
Definition: utils.c:266
int ast_tvcmp(struct timeval _a, struct timeval _b)
Compress two struct timeval instances returning -1, 0, 1 if the first arg is smaller, equal or greater to the second.
Definition: time.h:137
int ast_strings_equal(const char *str1, const char *str2)
Compare strings for equality checking for NULL.
Definition: strings.c:238
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
Support for logging to various files, console and syslog Configuration in file logger.conf.
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
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:427
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
#define ast_string_field_build(x, field, fmt, args...)
Set a field to a complex (built) value.
Definition: stringfields.h:555
struct timeval ast_mktime(struct ast_tm *const tmp, const char *zone)
Timezone-independent version of mktime(3).
Definition: localtime.c:2357
Standard Command Line Interface.
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:407
int ast_db_del(const char *family, const char *key)
Delete entry in astdb.
Definition: main/db.c:476
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
Profile configuration for stir/shaken.
static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
Checks whether a string begins with another.
Definition: strings.h:97
Abstract JSON element (object, array, string, int, ...).
int ast_db_put(const char *family, const char *key, const char *value)
Store value addressed by family/key.
Definition: main/db.c:342
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
Persistent data storage (akin to *doze registry)
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
int ast_db_deltree(const char *family, const char *keytree)
Delete one or more entries in astdb.
Definition: main/db.c:536
Sorcery Data Access Layer API.
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521