Asterisk - The Open Source Telephony Project  21.4.1
attestation.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 
19 #include <jwt.h>
20 
21 #define _TRACE_PREFIX_ "a",__LINE__, ""
22 
23 #include "asterisk.h"
24 #include "asterisk/module.h"
25 #include "asterisk/uuid.h"
26 #include "asterisk/json.h"
27 #include "asterisk/channel.h"
28 
29 #include "stir_shaken.h"
30 
31 static const char *as_rc_map[] = {
32  [AST_STIR_SHAKEN_AS_SUCCESS] = "success",
33  [AST_STIR_SHAKEN_AS_DISABLED] = "disabled",
34  [AST_STIR_SHAKEN_AS_INVALID_ARGUMENTS] = "invalid_arguments",
35  [AST_STIR_SHAKEN_AS_MISSING_PARAMETERS] = "missing_parameters",
36  [AST_STIR_SHAKEN_AS_INTERNAL_ERROR] = "internal_error",
37  [AST_STIR_SHAKEN_AS_NO_TN_FOR_CALLERID] = "no_tn_for_callerid",
38  [AST_STIR_SHAKEN_AS_NO_PRIVATE_KEY_AVAIL] = "no_private_key_avail",
39  [AST_STIR_SHAKEN_AS_NO_PUBLIC_CERT_URL_AVAIL] = "no_public_cert_url_avail",
40  [AST_STIR_SHAKEN_AS_NO_ATTEST_LEVEL] = "no_attest_level",
41  [AST_STIR_SHAKEN_AS_IDENTITY_HDR_EXISTS] = "identity_header_exists",
42  [AST_STIR_SHAKEN_AS_NO_TO_HDR] = "no_to_hdr",
43  [AST_STIR_SHAKEN_AS_TO_HDR_BAD_URI] = "to_hdr_bad_uri",
44  [AST_STIR_SHAKEN_AS_SIGN_ENCODE_FAILURE] "sign_encode_failure",
45 };
46 
47 const char *as_response_code_to_str(
48  enum ast_stir_shaken_as_response_code as_rc)
49 {
50  return ARRAY_IN_BOUNDS(as_rc, as_rc_map) ?
51  as_rc_map[as_rc] : NULL;
52 }
53 
54 static void ctx_destructor(void *obj)
55 {
56  struct ast_stir_shaken_as_ctx *ctx = obj;
57 
58  ao2_cleanup(ctx->etn);
59  ast_channel_cleanup(ctx->chan);
61  AST_VECTOR_RESET(&ctx->fingerprints, ast_free);
62  AST_VECTOR_FREE(&ctx->fingerprints);
63 }
64 
65 enum ast_stir_shaken_as_response_code
66  ast_stir_shaken_as_ctx_create(const char *orig_tn,
67  const char *dest_tn, struct ast_channel *chan,
68  const char *profile_name,
69  const char *tag, struct ast_stir_shaken_as_ctx **ctxout)
70 {
71  RAII_VAR(struct ast_stir_shaken_as_ctx *, ctx, NULL, ao2_cleanup);
72  RAII_VAR(struct profile_cfg *, eprofile, NULL, ao2_cleanup);
73  RAII_VAR(struct attestation_cfg *, as_cfg, NULL, ao2_cleanup);
74  RAII_VAR(struct tn_cfg *, etn, NULL, ao2_cleanup);
75  RAII_VAR(char *, canon_dest_tn , canonicalize_tn_alloc(dest_tn), ast_free);
76  RAII_VAR(char *, canon_orig_tn , canonicalize_tn_alloc(orig_tn), ast_free);
77 
78  SCOPE_ENTER(3, "%s: Enter\n", tag);
79 
80  if (!canon_orig_tn) {
81  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INVALID_ARGUMENTS,
82  LOG_ERROR, "%s: Must provide caller_id/orig_tn\n", tag);
83  }
84 
85  if (!canon_dest_tn) {
86  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INVALID_ARGUMENTS,
87  LOG_ERROR, "%s: Must provide dest_tn\n", tag);
88  }
89 
90  if (ast_strlen_zero(tag)) {
91  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INVALID_ARGUMENTS,
92  LOG_ERROR, "%s: Must provide tag\n", tag);
93  }
94 
95  if (!ctxout) {
96  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INVALID_ARGUMENTS,
97  LOG_ERROR, "%s: Must provide ctxout\n", tag);
98  }
99 
100  if (ast_strlen_zero(profile_name)) {
101  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_DISABLED,
102  "%s: Disabled due to missing profile name\n", tag);
103  }
104 
105  as_cfg = as_get_cfg();
106  if (as_cfg->global_disable) {
107  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_DISABLED,
108  "%s: Globally disabled\n", tag);
109  }
110 
111  eprofile = eprofile_get_cfg(profile_name);
112  if (!eprofile) {
113  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_DISABLED,
114  LOG_ERROR, "%s: No profile for profile name '%s'. Call will continue\n", tag,
115  profile_name);
116  }
117 
118  if (!PROFILE_ALLOW_ATTEST(eprofile)) {
119  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_DISABLED,
120  "%s: Disabled by profile\n", tag);
121  }
122 
123  etn = tn_get_etn(canon_orig_tn, eprofile);
124  if (!etn) {
125  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_DISABLED,
126  "%s: No tn for orig_tn '%s'\n", tag, canon_orig_tn);
127  }
128 
129  /* We don't need eprofile or as_cfg anymore so let's clean em up */
130  ao2_cleanup(as_cfg);
131  as_cfg = NULL;
132  ao2_cleanup(eprofile);
133  eprofile = NULL;
134 
135 
136  if (etn->acfg_common.attest_level == attest_level_NOT_SET) {
137  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_MISSING_PARAMETERS,
138  LOG_ERROR,
139  "'%s': No attest_level specified in tn, profile or attestation objects\n",
140  tag);
141  }
142 
143  if (ast_strlen_zero(etn->acfg_common.public_cert_url)) {
144  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_NO_PUBLIC_CERT_URL_AVAIL,
145  LOG_ERROR, "%s: No public cert url in tn %s, profile or attestation objects\n",
146  tag, canon_orig_tn);
147  }
148 
149  if (etn->acfg_common.raw_key_length == 0) {
150  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_NO_PRIVATE_KEY_AVAIL,
151  LOG_ERROR, "%s: No private key in tn %s, profile or attestation objects\n",
152  canon_orig_tn, tag);
153  }
154 
155  ctx = ao2_alloc_options(sizeof(*ctx), ctx_destructor,
157  if (!ctx) {
158  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
159  LOG_ERROR, "%s: Unable to allocate memory for ctx\n", tag);
160  }
161 
162  if (ast_string_field_init(ctx, 1024) != 0) {
163  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
164  LOG_ERROR, "%s: Unable to allocate memory for ctx\n", tag);
165  }
166 
167  if (ast_string_field_set(ctx, tag, tag) != 0) {
168  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
169  LOG_ERROR, "%s: Unable to allocate memory for ctx\n", tag);
170  }
171 
172  if (ast_string_field_set(ctx, orig_tn, canon_orig_tn) != 0) {
173  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
174  LOG_ERROR, "%s: Unable to allocate memory for ctx\n", tag);
175  }
176 
177  if (ast_string_field_set(ctx, dest_tn, canon_dest_tn)) {
178  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
179  LOG_ERROR, "%s: Unable to allocate memory for ctx\n", tag);
180  }
181 
182  ctx->chan = chan;
183  ast_channel_ref(ctx->chan);
184 
185  if (AST_VECTOR_INIT(&ctx->fingerprints, 1) != 0) {
186  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
187  LOG_ERROR, "%s: Unable to allocate memory for ctx\n", tag);
188  }
189 
190  /* Transfer the references */
191  ctx->etn = etn;
192  etn = NULL;
193  *ctxout = ctx;
194  ctx = NULL;
195 
196  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_SUCCESS, "%s: Done\n", tag);
197 }
198 
199 int ast_stir_shaken_as_ctx_wants_fingerprints(struct ast_stir_shaken_as_ctx *ctx)
200 {
201  return ENUM_BOOL(ctx->etn->acfg_common.send_mky, send_mky);
202 }
203 
204 enum ast_stir_shaken_as_response_code
205  ast_stir_shaken_as_ctx_add_fingerprint(
206  struct ast_stir_shaken_as_ctx *ctx, const char *alg, const char *fingerprint)
207 {
208  char *compacted_fp = ast_alloca(strlen(fingerprint) + 1);
209  const char *f = fingerprint;
210  char *fp = compacted_fp;
211  char *combined;
212  int rc;
213  SCOPE_ENTER(4, "%s: Add fingerprint %s:%s\n", ctx ? ctx->tag : "",
214  alg, fingerprint);
215 
216  if (!ctx || ast_strlen_zero(alg) || ast_strlen_zero(fingerprint)) {
217  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_INVALID_ARGUMENTS,
218  "%s: Missing arguments\n", ctx->tag);
219  }
220 
221  if (!ENUM_BOOL(ctx->etn->acfg_common.send_mky, send_mky)) {
222  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_DISABLED,
223  "%s: Not needed\n", ctx->tag);
224  }
225 
226  /* De-colonize */
227  while (*f != '\0') {
228  if (*f != ':') {
229  *fp++ = *f;
230  }
231  f++;
232  }
233  *fp = '\0';
234  rc = ast_asprintf(&combined, "%s:%s", alg, compacted_fp);
235  if (rc < 0) {
236  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
237  "%s: Can't allocate memory for comobined string\n", ctx->tag);
238  }
239 
240  rc = AST_VECTOR_ADD_SORTED(&ctx->fingerprints, combined, strcasecmp);
241  if (rc < 0) {
242  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
243  "%s: Can't add entry to vector\n", ctx->tag);
244  }
245 
246  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_SUCCESS,
247  "%s: Done\n", ctx->tag);
248 }
249 
250 /*
251  * We have to construct the PASSporT payload manually instead of
252  * using ast_json_pack. These macros help make sure nothing
253  * leaks if there are errors creating the individual objects.
254  */
255 #define CREATE_JSON_SET_OBJ(__val, __obj, __name) \
256 ({ \
257  struct ast_json *__var; \
258  if (!(__var = __val)) {\
259  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR, \
260  LOG_ERROR, "%s: Cannot allocate one of the JSON objects\n", \
261  ctx->tag); \
262  } else { \
263  if (ast_json_object_set(__obj, __name, __var)) { \
264  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR, \
265  LOG_ERROR, "%s: Cannot set one of the JSON objects\n", \
266  ctx->tag); \
267  } \
268  } \
269  (__var); \
270 })
271 
272 #define CREATE_JSON_APPEND_ARRAY(__val, __obj) \
273 ({ \
274  struct ast_json *__var; \
275  if (!(__var = __val)) {\
276  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR, \
277  LOG_ERROR, "%s: Cannot allocate one of the JSON objects\n", \
278  ctx->tag); \
279  } else { \
280  if (ast_json_array_append(__obj, __var)) { \
281  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR, \
282  LOG_ERROR, "%s: Cannot set one of the JSON objects\n", \
283  ctx->tag); \
284  } \
285  } \
286  (__var); \
287 })
288 
289 static enum ast_stir_shaken_as_response_code pack_payload(
290  struct ast_stir_shaken_as_ctx *ctx, jwt_t *jwt)
291 {
293  /*
294  * These don't need RAII because once they're added to payload,
295  * they'll get destroyed when payload gets unreffed.
296  */
297  struct ast_json *dest;
298  struct ast_json *tns;
299  struct ast_json *orig;
300  char origid[AST_UUID_STR_LEN];
301  char *payload_str = NULL;
302  SCOPE_ENTER(3, "%s: Enter\n", ctx->tag);
303 
304  /*
305  * All fields added need to be in alphabetical order
306  * and there must be no whitespace in the result.
307  *
308  * We can't use ast_json_pack here because the entries
309  * need to be kept in order and the "mky" array may
310  * not be present.
311  */
312 
313  /*
314  * The order of the calls matters. We want to add an object
315  * to its parent as soon as it's created, then add things
316  * to it. This way if something later fails, the whole thing
317  * will get destroyed when its parent gets destroyed.
318  */
319  CREATE_JSON_SET_OBJ(ast_json_string_create(
320  attest_level_to_str(ctx->etn->acfg_common.attest_level)),
321  payload, "attest");
322 
323  dest = CREATE_JSON_SET_OBJ(ast_json_object_create(), payload, "dest");
324  tns = CREATE_JSON_SET_OBJ(ast_json_array_create(), dest, "tn");
325  CREATE_JSON_APPEND_ARRAY(ast_json_string_create(ctx->dest_tn), tns);
326 
327  CREATE_JSON_SET_OBJ(ast_json_integer_create(time(NULL)), payload, "iat");
328 
329  if (AST_VECTOR_SIZE(&ctx->fingerprints)
330  && ENUM_BOOL(ctx->etn->acfg_common.send_mky, send_mky)) {
331  struct ast_json *mky;
332  int i;
333 
334  mky = CREATE_JSON_SET_OBJ(ast_json_array_create(), payload, "mky");
335 
336  for (i = 0; i < AST_VECTOR_SIZE(&ctx->fingerprints); i++) {
337  struct ast_json *mk;
338  char *afp = AST_VECTOR_GET(&ctx->fingerprints, i);
339  char *fp = strchr(afp, ':');
340  *fp++ = '\0';
341 
342  mk = CREATE_JSON_APPEND_ARRAY(ast_json_object_create(), mky);
343  CREATE_JSON_SET_OBJ(ast_json_string_create(afp), mk, "alg");
344  CREATE_JSON_SET_OBJ(ast_json_string_create(fp), mk, "dig");
345  }
346  }
347 
348  orig = CREATE_JSON_SET_OBJ(ast_json_object_create(), payload, "orig");
349  CREATE_JSON_SET_OBJ(ast_json_string_create(ctx->orig_tn), orig, "tn");
350 
351  ast_uuid_generate_str(origid, sizeof(origid));
352  CREATE_JSON_SET_OBJ(ast_json_string_create(origid), payload, "origid");
353 
354  payload_str = ast_json_dump_string_format(payload, AST_JSON_COMPACT);
355  ast_trace(2, "Payload: %s\n", payload_str);
356  jwt_add_grants_json(jwt, payload_str);
357  ast_json_free(payload_str);
358 
359  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_SUCCESS, "Done\n");
360 
361 }
362 
363 enum ast_stir_shaken_as_response_code ast_stir_shaken_attest(
364  struct ast_stir_shaken_as_ctx *ctx, char **header)
365 {
366  RAII_VAR(jwt_t *, jwt, NULL, jwt_free);
367  jwt_alg_t alg;
368  char *encoded = NULL;
369  enum ast_stir_shaken_as_response_code as_rc;
370  int rc = 0;
371  SCOPE_ENTER(3, "%s: Attestation: orig: %s dest: %s\n",
372  ctx ? ctx->tag : "NULL", ctx ? ctx->orig_tn : "NULL",
373  ctx ? ctx->dest_tn : "NULL");
374 
375  if (!ctx) {
376  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_VS_INTERNAL_ERROR, LOG_ERROR,
377  "%s: No context object!\n", "NULL");
378  }
379 
380  if (header == NULL) {
381  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INVALID_ARGUMENTS,
382  LOG_ERROR, "%s: Header buffer was NULL\n", ctx->tag);
383  }
384 
385  rc = jwt_new(&jwt);
386  if (rc != 0) {
387  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
388  LOG_ERROR, "%s: Cannot create JWT\n", ctx->tag);
389  }
390 
391  /*
392  * All headers added need to be in alphabetical order!
393  */
394  alg = jwt_str_alg(STIR_SHAKEN_ENCRYPTION_ALGORITHM);
395  jwt_set_alg(jwt, alg, (const unsigned char *)ctx->etn->acfg_common.raw_key,
396  ctx->etn->acfg_common.raw_key_length);
397  jwt_add_header(jwt, "ppt", STIR_SHAKEN_PPT);
398  jwt_add_header(jwt, "typ", STIR_SHAKEN_TYPE);
399  jwt_add_header(jwt, "x5u", ctx->etn->acfg_common.public_cert_url);
400 
401  as_rc = pack_payload(ctx, jwt);
402  if (as_rc != AST_STIR_SHAKEN_AS_SUCCESS) {
403  SCOPE_EXIT_LOG_RTN_VALUE(as_rc,
404  LOG_ERROR, "%s: Cannot pack payload\n", ctx->tag);
405  }
406 
407  encoded = jwt_encode_str(jwt);
408  if (!encoded) {
409  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_SIGN_ENCODE_FAILURE,
410  LOG_ERROR, "%s: Unable to sign/encode JWT\n", ctx->tag);
411  }
412 
413  rc = ast_asprintf(header, "%s;info=<%s>;alg=%s;ppt=%s",
414  encoded, ctx->etn->acfg_common.public_cert_url, jwt_alg_str(alg),
415  STIR_SHAKEN_PPT);
416  ast_std_free(encoded);
417  if (rc < 0) {
418  SCOPE_EXIT_LOG_RTN_VALUE(AST_STIR_SHAKEN_AS_INTERNAL_ERROR,
419  LOG_ERROR, "%s: Unable to allocate memory for identity header\n",
420  ctx->tag);
421  }
422 
423  SCOPE_EXIT_RTN_VALUE(AST_STIR_SHAKEN_AS_SUCCESS, "%s: Done\n", ctx->tag);
424 }
425 
426 int as_reload()
427 {
428  as_config_reload();
429 
430  return 0;
431 }
432 
433 int as_unload()
434 {
435  as_config_unload();
436  return 0;
437 }
438 
439 int as_load()
440 {
441  if (as_config_load()) {
443  }
444 
446 }
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
#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.
#define AST_VECTOR_ADD_SORTED(vec, elem, cmp)
Add an element into a sorted vector.
Definition: vector.h:371
Asterisk main include file. File version handling, generic pbx functions.
TN configuration for stir/shaken.
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
Universally unique identifier support.
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
General Asterisk PBX channel definitions.
Asterisk JSON abstraction layer.
struct ast_json * ast_json_string_create(const char *value)
Construct a JSON string from value.
Definition: json.c:278
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ast_channel_cleanup(c)
Cleanup a channel reference.
Definition: channel.h:2969
char * ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format)
Encode a JSON value to a string.
Definition: json.c:484
struct ast_json * ast_json_array_create(void)
Create a empty JSON array.
Definition: json.c:362
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define AST_VECTOR_RESET(vec, cleanup)
Reset vector.
Definition: vector.h:625
char * ast_uuid_generate_str(char *buf, size_t size)
Generate a UUID string.
Definition: uuid.c:141
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
struct ast_json * ast_json_object_create(void)
Create a new JSON object.
Definition: json.c:399
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680
#define ast_channel_ref(c)
Increase channel reference count.
Definition: channel.h:2947
Profile configuration for stir/shaken.
Abstract JSON element (object, array, string, int, ...).
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
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:609
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521
struct ast_json * ast_json_integer_create(intmax_t value)
Create a JSON integer.
Definition: json.c:327