Asterisk - The Open Source Telephony Project GIT-master-1f1c5bb
attestation.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
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
31static 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
49{
50 return ARRAY_IN_BOUNDS(as_rc, as_rc_map) ?
51 as_rc_map[as_rc] : NULL;
52}
53
54static void ctx_destructor(void *obj)
55{
56 struct ast_stir_shaken_as_ctx *ctx = obj;
57
58 ao2_cleanup(ctx->etn);
63}
64
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{
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) {
82 LOG_ERROR, "%s: Must provide caller_id/orig_tn\n", tag);
83 }
84
85 if (!canon_dest_tn) {
87 LOG_ERROR, "%s: Must provide dest_tn\n", tag);
88 }
89
90 if (ast_strlen_zero(tag)) {
92 LOG_ERROR, "%s: Must provide tag\n", tag);
93 }
94
95 if (!ctxout) {
97 LOG_ERROR, "%s: Must provide ctxout\n", tag);
98 }
99
100 if (ast_strlen_zero(profile_name)) {
102 "%s: Disabled due to missing profile name\n", tag);
103 }
104
105 as_cfg = as_get_cfg();
106 if (as_cfg->global_disable) {
108 "%s: Globally disabled\n", tag);
109 }
110
111 eprofile = eprofile_get_cfg(profile_name);
112 if (!eprofile) {
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)) {
120 "%s: Disabled by profile\n", tag);
121 }
122
123 etn = tn_get_etn(canon_orig_tn, eprofile);
124 if (!etn) {
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) {
138 LOG_ERROR,
139 "'%s': No attest_level specified in tn, profile or attestation objects\n",
140 tag);
141 }
142
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) {
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) {
159 LOG_ERROR, "%s: Unable to allocate memory for ctx\n", tag);
160 }
161
162 if (ast_string_field_init(ctx, 1024) != 0) {
164 LOG_ERROR, "%s: Unable to allocate memory for ctx\n", tag);
165 }
166
167 if (ast_string_field_set(ctx, tag, tag) != 0) {
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) {
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)) {
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) {
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
197}
198
200{
201 return ENUM_BOOL(ctx->etn->acfg_common.send_mky, send_mky);
202}
203
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)) {
218 "%s: Missing arguments\n", ctx->tag);
219 }
220
221 if (!ENUM_BOOL(ctx->etn->acfg_common.send_mky, send_mky)) {
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) {
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) {
243 "%s: Can't add entry to vector\n", ctx->tag);
244 }
245
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
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 */
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");
326
327 CREATE_JSON_SET_OBJ(ast_json_integer_create(time(NULL)), payload, "iat");
328
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
345 }
346 }
347
348 orig = CREATE_JSON_SET_OBJ(ast_json_object_create(), payload, "orig");
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
360
361}
362
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;
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) {
377 "%s: No context object!\n", "NULL");
378 }
379
380 if (header == NULL) {
382 LOG_ERROR, "%s: Header buffer was NULL\n", ctx->tag);
383 }
384
385 rc = jwt_new(&jwt);
386 if (rc != 0) {
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,
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) {
404 LOG_ERROR, "%s: Cannot pack payload\n", ctx->tag);
405 }
406
407 encoded = jwt_encode_str(jwt);
408 if (!encoded) {
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),
416 ast_std_free(encoded);
417 if (rc < 0) {
419 LOG_ERROR, "%s: Unable to allocate memory for identity header\n",
420 ctx->tag);
421 }
422
424}
425
427{
429
430 return 0;
431}
432
434{
436 return 0;
437}
438
440{
441 if (as_config_load()) {
443 }
444
446}
Asterisk main include file. File version handling, generic pbx functions.
void ast_std_free(void *ptr)
Definition: astmm.c:1734
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define ast_free(a)
Definition: astmm.h:180
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
@ AO2_ALLOC_OPT_LOCK_NOLOCK
Definition: astobj2.h:367
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
#define ao2_alloc_options(data_size, destructor_fn, options)
Definition: astobj2.h:404
int as_load()
Load the stir/shaken attestation service.
Definition: attestation.c:439
static const char * as_rc_map[]
Definition: attestation.c:31
const char * as_response_code_to_str(enum ast_stir_shaken_as_response_code as_rc)
Return string version of AS response code.
Definition: attestation.c:47
enum ast_stir_shaken_as_response_code ast_stir_shaken_as_ctx_add_fingerprint(struct ast_stir_shaken_as_ctx *ctx, const char *alg, const char *fingerprint)
Add DTLS fingerprints to AS context.
Definition: attestation.c:205
#define CREATE_JSON_APPEND_ARRAY(__val, __obj)
Definition: attestation.c:272
int as_unload()
Load the stir/shaken attestation service.
Definition: attestation.c:433
#define CREATE_JSON_SET_OBJ(__val, __obj, __name)
Definition: attestation.c:255
static void ctx_destructor(void *obj)
Definition: attestation.c:54
int as_reload()
Load the stir/shaken attestation service.
Definition: attestation.c:426
enum ast_stir_shaken_as_response_code ast_stir_shaken_attest(struct ast_stir_shaken_as_ctx *ctx, char **header)
Attest and return Identity header value.
Definition: attestation.c:363
enum ast_stir_shaken_as_response_code ast_stir_shaken_as_ctx_create(const char *orig_tn, const char *dest_tn, struct ast_channel *chan, const char *profile_name, const char *tag, struct ast_stir_shaken_as_ctx **ctxout)
Create Attestation Service Context.
Definition: attestation.c:66
int ast_stir_shaken_as_ctx_wants_fingerprints(struct ast_stir_shaken_as_ctx *ctx)
Indicates if the AS context needs DTLS fingerprints.
Definition: attestation.c:199
static enum ast_stir_shaken_as_response_code pack_payload(struct ast_stir_shaken_as_ctx *ctx, jwt_t *jwt)
Definition: attestation.c:289
int as_config_unload(void)
int as_config_reload(void)
struct attestation_cfg * as_get_cfg(void)
int as_config_load(void)
General Asterisk PBX channel definitions.
#define ast_channel_ref(c)
Increase channel reference count.
Definition: channel.h:2947
#define ast_channel_cleanup(c)
Cleanup a channel reference.
Definition: channel.h:2969
char * canonicalize_tn_alloc(const char *tn)
Canonicalize a TN into nre buffer.
struct profile_cfg * eprofile_get_cfg(const char *id)
#define ENUM_BOOL(__enum1, __field)
#define PROFILE_ALLOW_ATTEST(__profile)
struct tn_cfg * tn_get_etn(const char *tn, struct profile_cfg *eprofile)
Definition: tn_config.c:116
#define SCOPE_EXIT_RTN_VALUE(__return_value,...)
#define SCOPE_EXIT_LOG_RTN_VALUE(__value, __log_level,...)
#define SCOPE_ENTER(level,...)
#define ast_trace(level,...)
#define LOG_ERROR
Asterisk JSON abstraction layer.
struct ast_json * ast_json_string_create(const char *value)
Construct a JSON string from value.
Definition: json.c:278
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
struct ast_json * ast_json_object_create(void)
Create a new JSON object.
Definition: json.c:399
struct ast_json * ast_json_integer_create(intmax_t value)
Create a JSON integer.
Definition: json.c:327
struct ast_json * ast_json_array_create(void)
Create a empty JSON array.
Definition: json.c:362
@ AST_JSON_COMPACT
Definition: json.h:793
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
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
ast_stir_shaken_as_response_code
@ AST_STIR_SHAKEN_AS_NO_TN_FOR_CALLERID
@ AST_STIR_SHAKEN_AS_INVALID_ARGUMENTS
@ AST_STIR_SHAKEN_AS_TO_HDR_BAD_URI
@ AST_STIR_SHAKEN_AS_MISSING_PARAMETERS
@ AST_STIR_SHAKEN_AS_NO_PRIVATE_KEY_AVAIL
@ AST_STIR_SHAKEN_AS_DISABLED
@ AST_STIR_SHAKEN_AS_SIGN_ENCODE_FAILURE
@ AST_STIR_SHAKEN_AS_NO_TO_HDR
@ AST_STIR_SHAKEN_AS_NO_PUBLIC_CERT_URL_AVAIL
@ AST_STIR_SHAKEN_AS_SUCCESS
@ AST_STIR_SHAKEN_AS_INTERNAL_ERROR
@ AST_STIR_SHAKEN_AS_IDENTITY_HDR_EXISTS
@ AST_STIR_SHAKEN_AS_NO_ATTEST_LEVEL
@ AST_STIR_SHAKEN_VS_INTERNAL_ERROR
#define NULL
Definition: resample.c:96
#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_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
Main Channel structure associated with a channel.
Abstract JSON element (object, array, string, int, ...).
const ast_string_field dest_tn
Definition: attestation.h:29
const ast_string_field orig_tn
Definition: attestation.h:29
struct tn_cfg * etn
Definition: attestation.h:32
const ast_string_field tag
Definition: attestation.h:29
struct ast_channel * chan
Definition: attestation.h:30
struct ast_vector_string fingerprints
Definition: attestation.h:31
enum attest_level_enum attest_level
unsigned char * raw_key
const ast_string_field public_cert_url
enum send_mky_enum send_mky
Profile configuration for stir/shaken.
TN configuration for stir/shaken.
struct attestation_cfg_common acfg_common
#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 ARRAY_IN_BOUNDS(v, a)
Checks to see if value is within the bounds of the given array.
Definition: utils.h:687
Universally unique identifier support.
#define AST_UUID_STR_LEN
Definition: uuid.h:27
char * ast_uuid_generate_str(char *buf, size_t size)
Generate a UUID string.
Definition: uuid.c:141
#define AST_VECTOR_RESET(vec, cleanup)
Reset vector.
Definition: vector.h:625
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:609
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition: vector.h:174
#define AST_VECTOR_ADD_SORTED(vec, elem, cmp)
Add an element into a sorted vector.
Definition: vector.h:371
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition: vector.h:113
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680