Asterisk - The Open Source Telephony Project GIT-master-754dea3
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
verification.c
Go to the documentation of this file.
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 <regex.h>
23
24#include "asterisk.h"
25
26#define _TRACE_PREFIX_ "v",__LINE__, ""
27
28#include "asterisk/channel.h"
29#include "asterisk/cli.h"
30#include "asterisk/config.h"
31#include "asterisk/module.h"
32#include "asterisk/sorcery.h"
33#include "asterisk/astdb.h"
35#include "asterisk/utils.h"
36#include "asterisk/paths.h"
37#include "asterisk/logger.h"
38#include "asterisk/acl.h"
39#include "asterisk/time.h"
40#include "asterisk/localtime.h"
41#include "asterisk/crypto.h"
42#include "asterisk/json.h"
43
44#include "stir_shaken.h"
45
46#define AST_DB_FAMILY "STIR_SHAKEN"
47
48static regex_t url_match_regex;
49
50/* Certificates should begin with this */
51#define BEGIN_CERTIFICATE_STR "-----BEGIN CERTIFICATE-----"
52
53static const char *vs_rc_map[] = {
54 [AST_STIR_SHAKEN_VS_SUCCESS] = "success",
55 [AST_STIR_SHAKEN_VS_DISABLED] = "disabled",
56 [AST_STIR_SHAKEN_VS_INVALID_ARGUMENTS] = "invalid_arguments",
57 [AST_STIR_SHAKEN_VS_INTERNAL_ERROR] = "internal_error",
58 [AST_STIR_SHAKEN_VS_NO_IDENTITY_HDR] = "missing_identity_hdr",
59 [AST_STIR_SHAKEN_VS_NO_DATE_HDR] = "missing_date_hdr",
60 [AST_STIR_SHAKEN_VS_DATE_HDR_PARSE_FAILURE] = "date_hdr_parse_failure",
61 [AST_STIR_SHAKEN_VS_DATE_HDR_EXPIRED] = "date_hdr_range_error",
62 [AST_STIR_SHAKEN_VS_NO_JWT_HDR] = "missing_jwt_hdr",
63 [AST_STIR_SHAKEN_VS_CERT_CACHE_MISS] = "cert_cache_miss",
64 [AST_STIR_SHAKEN_VS_CERT_CACHE_INVALID] = "cert_cache_invalid",
65 [AST_STIR_SHAKEN_VS_CERT_CACHE_EXPIRED] = "cert_cache_expired",
66 [AST_STIR_SHAKEN_VS_CERT_RETRIEVAL_FAILURE] = "cert_retrieval_failure",
67 [AST_STIR_SHAKEN_VS_CERT_CONTENTS_INVALID] = "cert_contents_invalid",
68 [AST_STIR_SHAKEN_VS_CERT_NOT_TRUSTED] = "cert_not_trusted",
69 [AST_STIR_SHAKEN_VS_CERT_DATE_INVALID] = "cert_date_failure",
70 [AST_STIR_SHAKEN_VS_CERT_NO_TN_AUTH_EXT] = "cert_no_tn_auth_ext",
71 [AST_STIR_SHAKEN_VS_CERT_NO_SPC_IN_TN_AUTH_EXT] = "cert_no_spc_in_auth_ext",
72 [AST_STIR_SHAKEN_VS_NO_RAW_KEY] = "no_raw_key",
73 [AST_STIR_SHAKEN_VS_SIGNATURE_VALIDATION] = "signature_validation",
74 [AST_STIR_SHAKEN_VS_NO_IAT] = "missing_iat",
75 [AST_STIR_SHAKEN_VS_IAT_EXPIRED] = "iat_range_error",
76 [AST_STIR_SHAKEN_VS_INVALID_OR_NO_PPT] = "invalid_or_no_ppt",
77 [AST_STIR_SHAKEN_VS_INVALID_OR_NO_ALG] = "invalid_or_no_alg",
78 [AST_STIR_SHAKEN_VS_INVALID_OR_NO_TYP] = "invalid_or_no_typ",
79 [AST_STIR_SHAKEN_VS_INVALID_OR_NO_GRANTS] = "invalid_or_no_grants",
80 [AST_STIR_SHAKEN_VS_INVALID_OR_NO_ATTEST] = "invalid_or_no_attest",
81 [AST_STIR_SHAKEN_VS_NO_ORIGID] = "missing_origid",
82 [AST_STIR_SHAKEN_VS_NO_ORIG_TN] = "missing_orig_tn",
83 [AST_STIR_SHAKEN_VS_CID_ORIG_TN_MISMATCH] = "cid_orig_tn_mismatch",
84 [AST_STIR_SHAKEN_VS_NO_DEST_TN] = "missing_dest_tn",
85 [AST_STIR_SHAKEN_VS_INVALID_HEADER] = "invalid_header",
86 [AST_STIR_SHAKEN_VS_INVALID_GRANT] = "invalid_grant",
87 [AST_STIR_SHAKEN_VS_INVALID_OR_NO_CID] = "invalid_or_no_callerid",
88};
89
92{
93 return ARRAY_IN_BOUNDS(vs_rc, vs_rc_map) ?
94 vs_rc_map[vs_rc] : NULL;
95}
96
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")) {
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
113 const char *cache_control_header, const char *expires_header)
114{
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
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
225static 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
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
264 if (!tn_exten) {
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);
277 }
278
279 if (ret != V_ASN1_CONSTRUCTED || tag != V_ASN1_SEQUENCE) {
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);
301 }
302
303 if (ret != V_ASN1_CONSTRUCTED || tag != ASN1_TAG_TNAUTH_SPC) {
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) {
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) {
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
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) {
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) {
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) {
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);
357 &ctx->raw_key);
358 if (ctx->raw_key_len <= 0) {
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);
368 LOG_ERROR, "%s: Cert '%s' dates not valid\n",
369 ctx->tag, ctx->public_url);
370 }
371
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
378 struct ast_stir_shaken_vs_ctx *ctx)
379{
380 FILE *cert_file;
381 long http_code;
382 int rc = 0;
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) {
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) {
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,
418 write_data, header_data, open_socket_data);
419
420 if (http_code / 100 != 2) {
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)) {
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) {
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;
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;
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;
480 LOG_ERROR, "%s: Unable to add cert '%s' to ASTDB\n",
481 ctx->tag, ctx->public_url);
482 }
483
485 "%s: Cert '%s' successfully retrieved from internet and cached\n",
486 ctx->tag, ctx->public_url);
487}
488
491{
492 int rc = 0;
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")) {
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) {
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)) {
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
523 "%s: Cert file '%s' cache entry was expired for '%s'\n",
524 ctx->tag, ctx->filename, ctx->public_url);
525 }
526
528 if (!ctx->xcert) {
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
544 "%s: Cert '%s' successfully retrieved from cache\n",
545 ctx->tag, ctx->public_url);
546}
547
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) {
556 }
557
558 if (ast_string_field_build(ctx, filename, "%s/%s.pem",
559 ctx->eprofile->vcfg_common.cert_cache_dir, hash) != 0) {
561 }
562
563 if (ast_string_field_build(ctx, hash_family, "%s/hash/%s",
564 AST_DB_FAMILY, hash) != 0) {
566 }
567
568 if (ast_string_field_build(ctx, url_family, "%s/url", AST_DB_FAMILY) != 0) {
570 }
571
573}
574
577{
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
600 "%s: Unable to retrieve cert '%s' from cache or internet\n",
601 ctx->tag, ctx->public_url);
602}
603
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 ?
610}
611
614 const char *date_hdr)
615{
616 return ast_string_field_set(ctx, date_hdr, date_hdr) == 0 ?
618}
619
622 struct ast_stir_shaken_vs_ctx *ctx)
623{
625}
626
628 struct ast_stir_shaken_vs_ctx *ctx)
629{
631}
632
634 struct ast_stir_shaken_vs_ctx *ctx)
635{
636 return ctx->caller_id;
637}
638
640 struct ast_stir_shaken_vs_ctx *ctx,
642{
643 ctx->failure_reason = vs_rc;
644}
645
646static void ctx_destructor(void *obj)
647{
648 struct ast_stir_shaken_vs_ctx *ctx = obj;
649
650 ao2_cleanup(ctx->eprofile);
651 ast_free(ctx->raw_key);
653 X509_free(ctx->xcert);
654}
655
658 struct ast_channel *chan, const char *profile_name,
659 const char *tag, struct ast_stir_shaken_vs_ctx **ctxout)
660{
662 RAII_VAR(struct profile_cfg *, profile, NULL, ao2_cleanup);
664 RAII_VAR(char *, canon_caller_id , canonicalize_tn_alloc(caller_id), ast_free);
665
666 const char *t = S_OR(tag, S_COR(chan, ast_channel_name(chan), ""));
667 SCOPE_ENTER(3, "%s: Enter\n", t);
668
669 vs = vs_get_cfg();
670 if (vs->global_disable) {
672 "%s: Globally disabled\n", t);
673 }
674
675 if (ast_strlen_zero(profile_name)) {
677 "%s: Disabled due to missing profile name\n", t);
678 }
679
680 profile = eprofile_get_cfg(profile_name);
681 if (!profile) {
683 LOG_ERROR, "%s: No profile for profile name '%s'. Call will continue\n", tag,
684 profile_name);
685 }
686
687 if (!PROFILE_ALLOW_VERIFY(profile)) {
689 "%s: Disabled by profile '%s'\n", t, profile_name);
690 }
691
692 if (ast_strlen_zero(tag)) {
694 LOG_ERROR, "%s: Must provide tag\n", t);
695 }
696
697 ctx = ao2_alloc_options(sizeof(*ctx), ctx_destructor,
699 if (!ctx) {
701 }
702 if (ast_string_field_init(ctx, 1024) != 0) {
704 }
705
706 if (ast_string_field_set(ctx, tag, tag) != 0) {
708 }
709
710 ctx->chan = chan;
711 if (ast_string_field_set(ctx, caller_id, canon_caller_id) != 0) {
713 }
714
715 /* Transfer references to ctx */
716 ctx->eprofile = profile;
717 profile = NULL;
718
719 ao2_ref(ctx, +1);
720 *ctxout = ctx;
722}
723
725 struct ast_stir_shaken_vs_ctx * ctx)
726{
727 struct ast_tm date_hdr_tm;
728 struct timeval date_hdr_timeval;
729 struct timeval current_timeval;
730 char *remainder;
731 char timezone[80] = { 0 };
732 int64_t time_diff;
733 SCOPE_ENTER(3, "%s: Checking date header: '%s'\n",
734 ctx->tag, ctx->date_hdr);
735
736 if (!(remainder = ast_strptime(ctx->date_hdr, "%a, %d %b %Y %T", &date_hdr_tm))) {
738 LOG_ERROR, "%s: Failed to parse: '%s'\n",
739 ctx->tag, ctx->date_hdr);
740 }
741
742 sscanf(remainder, "%79s", timezone);
743
744 if (ast_strlen_zero(timezone)) {
746 LOG_ERROR, "%s: A timezone is required: '%s'\n",
747 ctx->tag, ctx->date_hdr);
748 }
749
750 date_hdr_timeval = ast_mktime(&date_hdr_tm, timezone);
751 ctx->date_hdr_time = date_hdr_timeval.tv_sec;
752 current_timeval = ast_tvnow();
753
754 time_diff = ast_tvdiff_ms(current_timeval, date_hdr_timeval);
755 ast_trace(3, "%zu %zu %zu %d\n", current_timeval.tv_sec,
756 date_hdr_timeval.tv_sec,
757 (current_timeval.tv_sec - date_hdr_timeval.tv_sec), (int)time_diff);
758 if (time_diff < 0) {
759 /* An INVITE from the future! */
761 LOG_ERROR, "%s: Future date: '%s'\n",
762 ctx->tag, ctx->date_hdr);
763 } else if (time_diff > (ctx->eprofile->vcfg_common.max_date_header_age * 1000)) {
765 LOG_ERROR, "%s: More than %u seconds old: '%s'\n",
767 }
768
770 "%s: Success: '%s'\n", ctx->tag, ctx->date_hdr);
771}
772
773#define FULL_URL_REGEX "^([a-zA-Z]+)://(([^@]+@[^:]+):)?(([^:/?]+)|([0-9.]+)|([[][0-9a-fA-F:]+[]]))(:([0-9]+))?(/([^#\\?]+))?(\\?([^#]+))?(#(.*))?"
774#define FULL_URL_REGEX_GROUPS 15
775/*
776 * Broken down...
777 * ^([a-zA-Z]+) must start with scheme group 1
778 * ://
779 * (([^@]+@[^:]+):)? optional user@pass group 3
780 * ( start hostname group group 4
781 * ([^:/?]+) normal fqdn group 5
782 * |([0-9.]+) OR IPv4 address group 6
783 * |([[][0-9a-fA-F:]+[]]) OR IPv6 address group 7
784 * ) end hostname group
785 * (:([0-9]+))? optional port group 9
786 * (/([^#\?]+))? optional path group 11
787 * (\?([^#]+))? optional query string group 13
788 * (#([^?]+))? optional fagment group 15
789 *
790 * If you change the regex, make sure FULL_URL_REGEX_GROUPS is updated.
791 */
792#define URL_MATCH_SCHEME 1
793#define URL_MATCH_USERPASS 3
794#define URL_MATCH_HOST 4
795#define URL_MATCH_PORT 9
796#define URL_MATCH_PATH 11
797#define URL_MATCH_QUERY 13
798#define URL_MATCH_FRAGMENT 15
799
800#define get_match_string(__x5u, __pmatch, __i) \
801({ \
802 char *__match = NULL; \
803 if (__pmatch[__i].rm_so >= 0) { \
804 regoff_t __len = __pmatch[__i].rm_eo - __pmatch[__i].rm_so; \
805 const char *__start = __x5u + __pmatch[__i].rm_so; \
806 __match = ast_alloca(__len + 1); \
807 ast_copy_string(__match, __start, __len + 1); \
808 } \
809 __match; \
810})
811
812#define DUMP_X5U_MATCH() \
813{\
814 int i; \
815 if (TRACE_ATLEAST(4)) { \
816 ast_trace(-1, "%s: x5u: %s\n", ctx->tag, x5u); \
817 for (i=0;i<FULL_URL_REGEX_GROUPS;i++) { \
818 const char *m = get_match_string(x5u, pmatch, i); \
819 if (m) { \
820 ast_trace(-1, "%s: %2d %s\n", ctx->tag, i, m); \
821 } \
822 } \
823 } \
824}
825
826static int check_x5u_url(struct ast_stir_shaken_vs_ctx * ctx,
827 const char *x5u)
828{
829 int max_groups = url_match_regex.re_nsub + 1;
830 regmatch_t pmatch[max_groups];
831 int rc;
832 SCOPE_ENTER(3, "%s: Checking x5u '%s'\n", ctx->tag, x5u);
833
834 rc = regexec(&url_match_regex, x5u, max_groups, pmatch, 0);
835 if (rc) {
836 char regex_error[512];
837 regerror(rc, &url_match_regex, regex_error, sizeof(regex_error));
839 "%s: x5u '%s' in Identity header failed basic URL validation: %s\n",
840 ctx->tag, x5u, regex_error);
841 }
842
844 != relax_x5u_port_scheme_restrictions_YES) {
845 const char *scheme = get_match_string(x5u, pmatch, URL_MATCH_SCHEME);
846 const char *port = get_match_string(x5u, pmatch, URL_MATCH_PORT);
847
848 if (!ast_strings_equal(scheme, "https")) {
851 "%s: x5u '%s': scheme '%s' not https\n",
852 ctx->tag, x5u, scheme);
853 }
854 if (!ast_strlen_zero(port)) {
855 if (!ast_strings_equal(port, "443")
856 || !ast_strings_equal(port, "8443")) {
859 "%s: x5u '%s': port '%s' not port 443 or 8443\n",
860 ctx->tag, x5u, port);
861 }
862 }
863 }
864
866 != relax_x5u_path_restrictions_YES) {
867 const char *userpass = get_match_string(x5u, pmatch, URL_MATCH_USERPASS);
868 const char *qs = get_match_string(x5u, pmatch, URL_MATCH_QUERY);
869 const char *frag = get_match_string(x5u, pmatch, URL_MATCH_FRAGMENT);
870
871 if (!ast_strlen_zero(userpass) || !ast_strlen_zero(qs)
872 || !ast_strlen_zero(frag)) {
875 "%s: x5u '%s' contains user:password, query parameters or fragment\n",
876 ctx->tag, x5u);
877 }
878 }
879
880 return 0;
881}
882
885{
886 RAII_VAR(char *, jwt_encoded, NULL, ast_free);
887 RAII_VAR(jwt_t *, jwt, NULL, jwt_free);
888 RAII_VAR(struct ast_json *, grants, NULL, ast_json_unref);
889 char *p = NULL;
890 char *grants_str = NULL;
891 const char *x5u;
892 const char *ppt_header = NULL;
893 const char *grant = NULL;
894 time_t now_s = time(NULL);
895 time_t iat;
896 struct ast_json *grant_obj = NULL;
897 int len;
898 int rc;
900 SCOPE_ENTER(3, "%s: Verifying\n", ctx ? ctx->tag : "NULL");
901
902 if (!ctx) {
904 "%s: No context object!\n", "NULL");
905 }
906
907 if (ast_strlen_zero(ctx->identity_hdr)) {
909 "%s: No identity header in ctx\n", ctx->tag);
910 }
911
912 p = strchr(ctx->identity_hdr, ';');
913 len = p - ctx->identity_hdr + 1;
914 jwt_encoded = ast_malloc(len);
915 if (!jwt_encoded) {
917 "%s: Failed to allocate memory for encoded jwt\n", ctx->tag);
918 }
919
920 memcpy(jwt_encoded, ctx->identity_hdr, len);
921 jwt_encoded[len - 1] = '\0';
922
923 jwt_decode(&jwt, jwt_encoded, NULL, 0);
924
925 ppt_header = jwt_get_header(jwt, "ppt");
926 if (!ppt_header || strcmp(ppt_header, STIR_SHAKEN_PPT)) {
929 }
930
931 vs_rc = check_date_header(ctx);
932 if (vs_rc != AST_STIR_SHAKEN_VS_SUCCESS) {
934 "%s: Date header verification failed\n", ctx->tag);
935 }
936
937 x5u = jwt_get_header(jwt, "x5u");
938 if (ast_strlen_zero(x5u)) {
940 "%s: No x5u in Identity header\n", ctx->tag);
941 }
942
943 rc = check_x5u_url(ctx, x5u);
944 if (rc != AST_STIR_SHAKEN_VS_SUCCESS) {
946 "%s: x5u URL verification failed\n", ctx->tag);
947 }
948
949 ast_trace(3, "%s: Decoded enough to get x5u: '%s'\n", ctx->tag, x5u);
950 if (ast_string_field_set(ctx, public_url, x5u) != 0) {
952 "%s: Failed to set public_url '%s'\n", ctx->tag, x5u);
953 }
954
955 iat = jwt_get_grant_int(jwt, "iat");
956 if (iat == 0) {
958 "%s: No 'iat' in Identity header\n", ctx->tag);
959 }
960 ast_trace(1, "date_hdr: %zu iat: %zu diff: %zu\n",
961 ctx->date_hdr_time, iat, ctx->date_hdr_time - iat);
962 if (iat + ctx->eprofile->vcfg_common.max_iat_age < now_s) {
964 "%s: iat %ld older than %u seconds\n", ctx->tag,
965 iat, ctx->eprofile->vcfg_common.max_iat_age);
966 }
967 ctx->validity_check_time = iat;
968
969 vs_rc = ctx_populate(ctx);
970 if (vs_rc != AST_STIR_SHAKEN_VS_SUCCESS) {
972 "%s: Unable to populate ctx\n", ctx->tag);
973 }
974
975 vs_rc = retrieve_verification_cert(ctx);
976 if (vs_rc != AST_STIR_SHAKEN_VS_SUCCESS) {
978 "%s: Could not get valid cert from '%s'\n", ctx->tag, ctx->public_url);
979 }
980
981 jwt_free(jwt);
982 jwt = NULL;
983
984 rc = jwt_decode(&jwt, jwt_encoded, ctx->raw_key, ctx->raw_key_len);
985 if (rc != 0) {
987 LOG_ERROR, "%s: Signature validation failed for '%s'\n",
988 ctx->tag, ctx->public_url);
989 }
990
991 ast_trace(1, "%s: Decoding succeeded\n", ctx->tag);
992
993 ppt_header = jwt_get_header(jwt, "alg");
994 if (!ppt_header || strcmp(ppt_header, STIR_SHAKEN_ENCRYPTION_ALGORITHM)) {
996 "%s: %s\n", ctx->tag,
998 }
999
1000 ppt_header = jwt_get_header(jwt, "ppt");
1001 if (!ppt_header || strcmp(ppt_header, STIR_SHAKEN_PPT)) {
1003 "%s: %s\n", ctx->tag,
1005 }
1006
1007 ppt_header = jwt_get_header(jwt, "typ");
1008 if (!ppt_header || strcmp(ppt_header, STIR_SHAKEN_TYPE)) {
1010 "%s: %s\n", ctx->tag,
1012 }
1013
1014 grants_str = jwt_get_grants_json(jwt, NULL);
1015 if (ast_strlen_zero(grants_str)) {
1017 "%s: %s\n", ctx->tag,
1019 }
1020 ast_trace(1, "grants: %s\n", grants_str);
1021 grants = ast_json_load_string(grants_str, NULL);
1022 ast_std_free(grants_str);
1023 if (!grants) {
1025 "%s: %s\n", ctx->tag,
1027 }
1028
1029 grant = ast_json_object_string_get(grants, "attest");
1030 if (ast_strlen_zero(grant)) {
1032 "%s: No 'attest' in Identity header\n", ctx->tag);
1033 }
1034 if (grant[0] < 'A' || grant[0] > 'C') {
1036 "%s: Invalid attest value '%s'\n", ctx->tag, grant);
1037 }
1038 ast_string_field_set(ctx, attestation, grant);
1039 ast_trace(1, "got attest: %s\n", grant);
1040
1041 grant_obj = ast_json_object_get(grants, "dest");
1042 if (!grant_obj) {
1044 "%s: No 'dest' in Identity header\n", ctx->tag);
1045 }
1046 if (TRACE_ATLEAST(3)) {
1047 char *otn = ast_json_dump_string(grant_obj);
1048 ast_trace(1, "got dest: %s\n", otn);
1049 ast_json_free(otn);
1050 }
1051
1052 grant_obj = ast_json_object_get(grants, "orig");
1053 if (!grant_obj) {
1055 "%s: No 'orig' in Identity header\n", ctx->tag);
1056 }
1057 if (TRACE_ATLEAST(3)) {
1058 char *otn = ast_json_dump_string(grant_obj);
1059 ast_trace(1, "got orig: %s\n", otn);
1060 ast_json_free(otn);
1061 }
1062 grant = ast_json_object_string_get(grant_obj, "tn");
1063 if (!grant) {
1065 "%s: No 'orig.tn' in Indentity header\n", ctx->tag);
1066 }
1067 ast_string_field_set(ctx, orig_tn, grant);
1068 if (strcmp(ctx->caller_id, ctx->orig_tn) != 0) {
1070 "%s: Mismatched cid '%s' and orig_tn '%s'\n", ctx->tag,
1071 ctx->caller_id, grant);
1072 }
1073
1074 grant = ast_json_object_string_get(grants, "origid");
1075 if (ast_strlen_zero(grant)) {
1077 "%s: No 'origid' in Identity header\n", ctx->tag);
1078 }
1079
1081 "%s: verification succeeded\n", ctx->tag);
1082}
1083
1085{
1087
1088 return 0;
1089}
1090
1092{
1094 if (url_match_regex.re_nsub > 0) {
1095 regfree(&url_match_regex);
1096 }
1097
1098 return 0;
1099}
1100
1102{
1103 int rc = 0;
1104
1105 if (vs_config_load()) {
1107 }
1108
1109 rc = regcomp(&url_match_regex, FULL_URL_REGEX, REG_EXTENDED);
1110 if (rc) {
1111 char regex_error[512];
1112 regerror(rc, &url_match_regex, regex_error, sizeof(regex_error));
1113 ast_log(LOG_ERROR, "Verification service URL regex failed to compile: %s\n", regex_error);
1114 vs_unload();
1116 }
1117 if (url_match_regex.re_nsub != FULL_URL_REGEX_GROUPS) {
1118 ast_log(LOG_ERROR, "The verification service URL regex was updated without updating FULL_URL_REGEX_GROUPS\n");
1119 vs_unload();
1121 }
1122
1124}
Access Control of various sorts.
Persistent data storage (akin to *doze registry)
int ast_db_put(const char *family, const char *key, const char *value)
Store value addressed by family/key.
Definition: db.c:335
int ast_db_get(const char *family, const char *key, char *value, int valuelen)
Get key value specified by family/key.
Definition: db.c:421
int ast_db_del(const char *family, const char *key)
Delete entry in astdb.
Definition: db.c:472
int ast_db_deltree(const char *family, const char *keytree)
Delete one or more entries in astdb.
Definition: db.c:559
int ast_db_exists(const char *family, const char *key)
Check if family/key exitsts.
Definition: db.c:438
Asterisk main include file. File version handling, generic pbx functions.
void ast_std_free(void *ptr)
Definition: astmm.c:1734
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
#define ast_log
Definition: astobj2.c:42
@ AO2_ALLOC_OPT_LOCK_NOLOCK
Definition: astobj2.h:367
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ao2_alloc_options(data_size, destructor_fn, options)
Definition: astobj2.h:404
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
Standard Command Line Interface.
char * canonicalize_tn_alloc(const char *tn)
Canonicalize a TN into nre buffer.
int vs_config_reload(void)
struct verification_cfg * vs_get_cfg(void)
struct profile_cfg * eprofile_get_cfg(const char *id)
#define PROFILE_ALLOW_VERIFY(__profile)
int vs_config_load(void)
int vs_config_unload(void)
Conversion utility functions.
int ast_str_to_uint(const char *str, unsigned int *res)
Convert the given string to an unsigned integer.
Definition: conversions.c:56
int ast_str_to_ulong(const char *str, unsigned long *res)
Convert the given string to an unsigned long.
Definition: conversions.c:80
Provide cryptographic signature routines.
X509 * crypto_load_cert_from_memory(const char *buffer, size_t size)
Load an X509 Cert from a NULL terminated buffer.
Definition: crypto_utils.c:213
int crypto_is_cert_trusted(struct crypto_cert_store *store, X509 *cert, const char **err_msg)
Check if the cert is trusted.
Definition: crypto_utils.c:740
void crypto_log_openssl(int level, char *file, int line, const char *function, const char *fmt,...)
Print a log message with any OpenSSL errors appended.
Definition: crypto_utils.c:45
int crypto_get_raw_pubkey_from_cert(X509 *cert, unsigned char **buffer)
Retrieve RAW public key from cert.
Definition: crypto_utils.c:307
time_t crypto_asn_time_as_time_t(ASN1_TIME *at)
Return a time_t for an ASN1_TIME.
Definition: crypto_utils.c:770
char * crypto_get_cert_subject(X509 *cert, const char *short_name)
Returns the Subject (or component of Subject) from a certificate.
Definition: crypto_utils.c:787
int crypto_is_cert_time_valid(X509 *cert, time_t reftime)
Check if the reftime is within the cert's valid dates.
Definition: crypto_utils.c:721
X509 * crypto_load_cert_from_file(const char *filename)
Load an X509 Cert from a file.
Definition: crypto_utils.c:189
ASN1_OCTET_STRING * crypto_get_cert_extension_data(X509 *cert, int nid, const char *short_name)
Return the data from a specific extension in a cert.
Definition: crypto_utils.c:107
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
void curl_write_data_free(void *obj)
Definition: curl_utils.c:134
void curl_header_data_free(void *obj)
Definition: curl_utils.c:26
void curl_open_socket_data_free(void *obj)
Definition: curl_utils.c:193
#define TRACE_ATLEAST(level)
#define SCOPE_EXIT_RTN_VALUE(__return_value,...)
#define SCOPE_EXIT_LOG_RTN_VALUE(__value, __log_level,...)
#define SCOPE_ENTER(level,...)
#define ast_trace(level,...)
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
Configuration File Parser.
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:1013
Support for logging to various files, console and syslog Configuration in file logger....
#define LOG_ERROR
Asterisk JSON abstraction layer.
#define ast_json_object_string_get(object, key)
Get a string field from a JSON object.
Definition: json.h:600
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
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
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
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
Custom localtime functions for multiple timezones.
struct timeval ast_mktime(struct ast_tm *const tmp, const char *zone)
Timezone-independent version of mktime(3).
Definition: localtime.c:2357
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....
Definition: localtime.c:2550
#define remove
int errno
Asterisk module definitions.
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
Asterisk file paths, configured in asterisk.conf.
int get_tn_auth_nid(void)
Retrieves the OpenSSL NID for the TN Auth list extension.
ast_stir_shaken_vs_response_code
@ AST_STIR_SHAKEN_VS_CERT_DATE_INVALID
@ AST_STIR_SHAKEN_VS_NO_DATE_HDR
@ AST_STIR_SHAKEN_VS_SUCCESS
@ AST_STIR_SHAKEN_VS_INVALID_OR_NO_ATTEST
@ AST_STIR_SHAKEN_VS_INVALID_OR_NO_TYP
@ AST_STIR_SHAKEN_VS_CID_ORIG_TN_MISMATCH
@ AST_STIR_SHAKEN_VS_NO_JWT_HDR
@ AST_STIR_SHAKEN_VS_NO_DEST_TN
@ AST_STIR_SHAKEN_VS_IAT_EXPIRED
@ AST_STIR_SHAKEN_VS_NO_IDENTITY_HDR
@ AST_STIR_SHAKEN_VS_NO_RAW_KEY
@ AST_STIR_SHAKEN_VS_DATE_HDR_EXPIRED
@ AST_STIR_SHAKEN_VS_INVALID_ARGUMENTS
@ AST_STIR_SHAKEN_VS_SIGNATURE_VALIDATION
@ AST_STIR_SHAKEN_VS_INVALID_OR_NO_ALG
@ AST_STIR_SHAKEN_VS_CERT_CONTENTS_INVALID
@ AST_STIR_SHAKEN_VS_NO_ORIGID
@ AST_STIR_SHAKEN_VS_INVALID_HEADER
@ AST_STIR_SHAKEN_VS_CERT_CACHE_INVALID
@ AST_STIR_SHAKEN_VS_INVALID_GRANT
@ AST_STIR_SHAKEN_VS_DISABLED
@ AST_STIR_SHAKEN_VS_CERT_CACHE_MISS
@ AST_STIR_SHAKEN_VS_CERT_CACHE_EXPIRED
@ AST_STIR_SHAKEN_VS_NO_IAT
@ AST_STIR_SHAKEN_VS_NO_ORIG_TN
@ AST_STIR_SHAKEN_VS_INVALID_OR_NO_PPT
@ AST_STIR_SHAKEN_VS_CERT_NOT_TRUSTED
@ AST_STIR_SHAKEN_VS_DATE_HDR_PARSE_FAILURE
@ AST_STIR_SHAKEN_VS_INTERNAL_ERROR
@ AST_STIR_SHAKEN_VS_INVALID_OR_NO_CID
@ AST_STIR_SHAKEN_VS_CERT_NO_SPC_IN_TN_AUTH_EXT
@ AST_STIR_SHAKEN_VS_INVALID_OR_NO_GRANTS
@ AST_STIR_SHAKEN_VS_INVALID_OR_NO_X5U
@ AST_STIR_SHAKEN_VS_CERT_RETRIEVAL_FAILURE
@ AST_STIR_SHAKEN_VS_CERT_NO_TN_AUTH_EXT
stir_shaken_failure_action_enum
#define NULL
Definition: resample.c:96
Sorcery Data Access Layer API.
#define STIR_SHAKEN_ENCRYPTION_ALGORITHM
Definition: stir_shaken.h:28
#define STIR_SHAKEN_PPT
Definition: stir_shaken.h:29
#define STIR_SHAKEN_TYPE
Definition: stir_shaken.h:30
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ast_string_field_build(x, field, fmt, args...)
Set a field to a complex (built) value.
Definition: stringfields.h:555
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
int ast_strings_equal(const char *str1, const char *str2)
Compare strings for equality checking for NULL.
Definition: strings.c:238
#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
#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
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
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
Main Channel structure associated with a channel.
Abstract JSON element (object, array, string, int, ...).
const ast_string_field public_url
Definition: verification.h:39
const ast_string_field orig_tn
Definition: verification.h:39
const ast_string_field hash
Definition: verification.h:39
const ast_string_field hash_family
Definition: verification.h:39
unsigned char * raw_key
Definition: verification.h:45
const ast_string_field tag
Definition: verification.h:39
const ast_string_field date_hdr
Definition: verification.h:39
struct profile_cfg * eprofile
Definition: verification.h:40
const ast_string_field identity_hdr
Definition: verification.h:39
struct ast_channel * chan
Definition: verification.h:41
const ast_string_field filename
Definition: verification.h:39
const ast_string_field url_family
Definition: verification.h:39
const ast_string_field caller_id
Definition: verification.h:39
const ast_string_field cert_cn
Definition: verification.h:39
const ast_string_field cert_spc
Definition: verification.h:39
enum ast_stir_shaken_vs_response_code failure_reason
Definition: verification.h:48
int tm_isdst
Definition: localtime.h:44
Context structure passed to ast_curl_header_default_cb.
Definition: curl_utils.h:163
Context structure passed to ast_curl_open_socket_default_cb.
Definition: curl_utils.h:341
Context structure passed to ast_curl_write_default_cb.
Definition: curl_utils.h:245
Data structure used for ast_sip_push_task_wait_serializer
Profile configuration for stir/shaken.
struct verification_cfg_common vcfg_common
struct crypto_cert_store * tcs
enum stir_shaken_failure_action_enum stir_shaken_failure_action
enum use_rfc9410_responses_enum use_rfc9410_responses
const ast_string_field cert_cache_dir
enum relax_x5u_path_restrictions_enum relax_x5u_path_restrictions
enum relax_x5u_port_scheme_restrictions_enum relax_x5u_port_scheme_restrictions
unsigned int max_date_header_age
struct ast_acl_list * acl
Time-related functions and macros.
int ast_tvcmp(struct timeval _a, struct timeval _b)
Compress two struct timeval instances returning -1, 0, 1 if the first arg is smaller,...
Definition: time.h:137
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
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
Utility functions.
#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_file_is_readable(const char *filename)
Test that a file exists and is readable by the effective user.
Definition: utils.c:3107
#define ARRAY_IN_BOUNDS(v, a)
Checks to see if value is within the bounds of the given array.
Definition: utils.h:687
void ast_sha1_hash(char *output, const char *input)
Produces SHA1 hash based on input string.
Definition: utils.c:266
static enum ast_stir_shaken_vs_response_code retrieve_cert_from_url(struct ast_stir_shaken_vs_ctx *ctx)
Definition: verification.c:377
static regex_t url_match_regex
Definition: verification.c:48
enum ast_stir_shaken_vs_response_code ast_stir_shaken_vs_ctx_add_date_hdr(struct ast_stir_shaken_vs_ctx *ctx, const char *date_hdr)
Add the received Date header value to the VS context.
Definition: verification.c:613
static const char * vs_rc_map[]
Definition: verification.c:53
static int check_x5u_url(struct ast_stir_shaken_vs_ctx *ctx, const char *x5u)
Definition: verification.c:826
#define FULL_URL_REGEX_GROUPS
Definition: verification.c:774
#define URL_MATCH_USERPASS
Definition: verification.c:793
const char * ast_stir_shaken_vs_get_caller_id(struct ast_stir_shaken_vs_ctx *ctx)
Get caller_id from context.
Definition: verification.c:633
#define IS_GET_OBJ_ERR(ret)
Definition: verification.c:251
static int is_cert_cache_entry_expired(char *expiration)
Definition: verification.c:225
static enum ast_stir_shaken_vs_response_code check_cert(struct ast_stir_shaken_vs_ctx *ctx)
Definition: verification.c:326
enum stir_shaken_failure_action_enum ast_stir_shaken_vs_get_failure_action(struct ast_stir_shaken_vs_ctx *ctx)
Get failure_action from context.
Definition: verification.c:621
const char * vs_response_code_to_str(enum ast_stir_shaken_vs_response_code vs_rc)
Return string version of VS response code.
Definition: verification.c:90
#define URL_MATCH_SCHEME
Definition: verification.c:792
#define URL_MATCH_PORT
Definition: verification.c:795
#define get_match_string(__x5u, __pmatch, __i)
Definition: verification.c:800
#define ASN1_TAG_TNAUTH_SPC
Definition: verification.c:247
static enum ast_stir_shaken_vs_response_code retrieve_verification_cert(struct ast_stir_shaken_vs_ctx *ctx)
Definition: verification.c:576
#define FULL_URL_REGEX
Definition: verification.c:773
static int add_cert_expiration_to_astdb(struct ast_stir_shaken_vs_ctx *cert, const char *cache_control_header, const char *expires_header)
Definition: verification.c:112
static enum ast_stir_shaken_vs_response_code check_date_header(struct ast_stir_shaken_vs_ctx *ctx)
Definition: verification.c:724
enum ast_stir_shaken_vs_response_code ast_stir_shaken_vs_verify(struct ast_stir_shaken_vs_ctx *ctx)
Perform incoming call verification.
Definition: verification.c:884
int ast_stir_shaken_vs_get_use_rfc9410_responses(struct ast_stir_shaken_vs_ctx *ctx)
Get use_rfc9410_responses from context.
Definition: verification.c:627
#define URL_MATCH_FRAGMENT
Definition: verification.c:798
#define DUMP_X5U_MATCH()
Definition: verification.c:812
enum ast_stir_shaken_vs_response_code ast_stir_shaken_vs_ctx_create(const char *caller_id, struct ast_channel *chan, const char *profile_name, const char *tag, struct ast_stir_shaken_vs_ctx **ctxout)
Create Verification Service context.
Definition: verification.c:657
enum ast_stir_shaken_vs_response_code ast_stir_shaken_vs_ctx_add_identity_hdr(struct ast_stir_shaken_vs_ctx *ctx, const char *identity_hdr)
Add the received Identity header value to the VS context.
Definition: verification.c:605
static void ctx_destructor(void *obj)
Definition: verification.c:646
static int add_cert_key_to_astdb(struct ast_stir_shaken_vs_ctx *cert, const char *cache_control_hdr, const char *expires_hdr)
Definition: verification.c:201
#define BEGIN_CERTIFICATE_STR
Definition: verification.c:51
static enum ast_stir_shaken_vs_response_code check_tn_auth_list(struct ast_stir_shaken_vs_ctx *ctx)
Definition: verification.c:254
#define URL_MATCH_QUERY
Definition: verification.c:797
int vs_load()
Load the stir/shaken verification service.
int vs_unload()
Unload the stir/shaken verification service.
void ast_stir_shaken_vs_ctx_set_response_code(struct ast_stir_shaken_vs_ctx *ctx, enum ast_stir_shaken_vs_response_code vs_rc)
Sets response code on VS context.
Definition: verification.c:639
#define AST_DB_FAMILY
Definition: verification.c:46
int vs_reload()
Reload the stir/shaken verification service.
static enum ast_stir_shaken_vs_response_code retrieve_cert_from_cache(struct ast_stir_shaken_vs_ctx *ctx)
Definition: verification.c:490
static void cleanup_cert_from_astdb_and_fs(struct ast_stir_shaken_vs_ctx *ctx)
Definition: verification.c:97
static enum ast_stir_shaken_vs_response_code ctx_populate(struct ast_stir_shaken_vs_ctx *ctx)
Definition: verification.c:548