diff --git a/include/asterisk/json.h b/include/asterisk/json.h index 5edc3a9754..5b2d61422d 100644 --- a/include/asterisk/json.h +++ b/include/asterisk/json.h @@ -777,6 +777,8 @@ enum ast_json_encoding_format AST_JSON_COMPACT, /*! Formatted for human readability */ AST_JSON_PRETTY, + /*! Keys sorted alphabetically */ + AST_JSON_SORTED, }; /*! @@ -804,6 +806,17 @@ enum ast_json_encoding_format */ char *ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format); +/*! + * \brief Encode a JSON value to a string, with its keys sorted. + * + * Returned string must be freed by calling ast_json_free(). + * + * \param root JSON value. + * \return String encoding of \a root. + * \retval NULL on error. + */ +#define ast_json_dump_string_sorted(root) ast_json_dump_string_format(root, AST_JSON_SORTED) + #define ast_json_dump_str(root, dst) ast_json_dump_str_format(root, dst, AST_JSON_COMPACT) /*! diff --git a/main/json.c b/main/json.c index 616b12e67f..afb653a229 100644 --- a/main/json.c +++ b/main/json.c @@ -456,8 +456,19 @@ int ast_json_object_iter_set(struct ast_json *object, struct ast_json_iter *iter */ static size_t dump_flags(enum ast_json_encoding_format format) { - return format == AST_JSON_PRETTY ? - JSON_INDENT(2) | JSON_PRESERVE_ORDER : JSON_COMPACT; + size_t jansson_dump_flags; + + if (format & AST_JSON_PRETTY) { + jansson_dump_flags = JSON_INDENT(2); + } else { + jansson_dump_flags = JSON_COMPACT; + } + + if (format & AST_JSON_SORTED) { + jansson_dump_flags |= JSON_SORT_KEYS; + } + + return jansson_dump_flags; } char *ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format) diff --git a/res/ari/cli.c b/res/ari/cli.c index 9d0eb3099b..f9d9cecfb7 100644 --- a/res/ari/cli.c +++ b/res/ari/cli.c @@ -60,13 +60,10 @@ static char *ari_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) ast_cli(a->fd, "ARI Status:\n"); ast_cli(a->fd, "Enabled: %s\n", AST_CLI_YESNO(conf->general->enabled)); ast_cli(a->fd, "Output format: "); - switch (conf->general->format) { - case AST_JSON_COMPACT: - ast_cli(a->fd, "compact"); - break; - case AST_JSON_PRETTY: + if (conf->general->format & AST_JSON_PRETTY) { ast_cli(a->fd, "pretty"); - break; + } else { + ast_cli(a->fd, "compact"); } ast_cli(a->fd, "\n"); ast_cli(a->fd, "Auth realm: %s\n", conf->general->auth_realm); diff --git a/res/res_pjsip_stir_shaken.c b/res/res_pjsip_stir_shaken.c index 82c8df0f8b..1089e60a7c 100644 --- a/res/res_pjsip_stir_shaken.c +++ b/res/res_pjsip_stir_shaken.c @@ -399,7 +399,22 @@ static int add_identity_header(const struct ast_sip_session *session, pjsip_tx_d return -1; } - ast_copy_pj_str(dest_tn, &uri->user, uri->user.slen + 1); + /* Remove everything except 0-9, *, and # in telephone number according to RFC 8224 + * (required by RFC 8225 as part of canonicalization) */ + { + int i; + const char *s = uri->user.ptr; + char *new_tn = dest_tn; + /* We're only removing characters, if anything, so the buffer is guaranteed to be large enough */ + for (i = 0; i < uri->user.slen; i++) { + if (isdigit(*s) || *s == '#' || *s == '*') { /* Only characters allowed */ + *new_tn++ = *s; + } + s++; + } + *new_tn = '\0'; + ast_debug(4, "Canonicalized telephone number %.*s -> %s\n", (int) uri->user.slen, uri->user.ptr, dest_tn); + } /* x5u (public key URL), attestation, and origid will be added by ast_stir_shaken_sign */ json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: [s]}, s: {s: s}}}", @@ -427,7 +442,9 @@ static int add_identity_header(const struct ast_sip_session *session, pjsip_tx_d } payload = ast_json_object_get(json, "payload"); - dumped_string = ast_json_dump_string(payload); + /* Fields must appear in lexiographic order: https://www.rfc-editor.org/rfc/rfc8588.html#section-6 + * https://www.rfc-editor.org/rfc/rfc8225.html#section-9 */ + dumped_string = ast_json_dump_string_sorted(payload); encoded_payload = ast_base64url_encode_string(dumped_string); ast_json_free(dumped_string); if (!encoded_payload) { diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c index a4eae5bcc0..efb8be957d 100644 --- a/res/res_stir_shaken.c +++ b/res/res_stir_shaken.c @@ -1228,7 +1228,8 @@ struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json) tmp_json = ast_json_object_get(json, "header"); header = ast_json_dump_string(tmp_json); tmp_json = ast_json_object_get(json, "payload"); - payload = ast_json_dump_string(tmp_json); + + payload = ast_json_dump_string_sorted(tmp_json); msg_len = strlen(header) + strlen(payload) + 2; msg = ast_calloc(1, msg_len); if (!msg) { @@ -1661,7 +1662,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify) tmp_json = ast_json_object_get(json, "header"); header = ast_json_dump_string(tmp_json); tmp_json = ast_json_object_get(json, "payload"); - payload = ast_json_dump_string(tmp_json); + payload = ast_json_dump_string_sorted(tmp_json); /* Test empty header parameter */ returned_payload = ast_stir_shaken_verify("", payload, (const char *)signed_payload->signature,