diff --git a/configs/samples/stir_shaken.conf.sample b/configs/samples/stir_shaken.conf.sample index 1bd260641bb3801764a75b296056d97fafbda4f5..c39bc9726530e0366e4f978ae72b8dd3d2c61658 100644 --- a/configs/samples/stir_shaken.conf.sample +++ b/configs/samples/stir_shaken.conf.sample @@ -83,6 +83,3 @@ ; ; Must have an attestation of A, B, or C ;attestation=C -; -; The origination identifier for the certificate -;origid=MyAsterisk diff --git a/doc/UPGRADE-staging/stir_shaken_origid.txt b/doc/UPGRADE-staging/stir_shaken_origid.txt new file mode 100644 index 0000000000000000000000000000000000000000..f0b897757f49f603174b0d69d4749e45f886add5 --- /dev/null +++ b/doc/UPGRADE-staging/stir_shaken_origid.txt @@ -0,0 +1,8 @@ +Subject: STIR/SHAKEN + +STIR/SHAKEN originally needed an origid to be specified in +stir_shaken.conf under the certificate config object in +order to work. Now, one is automatically created by +generating a UUID, as recommended by RFC8588. Any origid +you have in your stir_shaken.conf will need to be removed +for the module to read in certificates. diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h index 0ee11ee5d53a95c646b57ae722df0cfd63a7c024..08120bf2204c4ba0238fd086252f814b1aac8e5c 100644 --- a/include/asterisk/utils.h +++ b/include/asterisk/utils.h @@ -276,6 +276,66 @@ int ast_base64decode(unsigned char *dst, const char *src, int max); */ char *ast_base64decode_string(const char *src); +/*! + * \brief Decode data from base64 URL + * + * \param dst The destination buffer + * \param src The source buffer + * \param max The maximum number of bytes to write into the destination + * buffer. Note that this function will not ensure that the + * destination buffer is NULL terminated. So, in general, + * this parameter should be sizeof(dst) - 1 + */ +int ast_base64url_decode(unsigned char *dst, const char *src, int max); + +/*! + * \brief Same as ast_base64encode_full but for base64 URL + * + * \param dst The destination buffer + * \param src The source buffer + * \param srclen The number of bytes present in the source buffer + * \param max The maximum number of bytes to write into the destination + * buffer, *including* the terminating NULL character. + * \param linebreaks Set to 1 if there should be linebreaks inserted + * in the result + */ +int ast_base64url_encode_full(char *dst, const unsigned char *src, int srclen, int max, int linebreaks); + +/*! + * \brief Encode data in base64 URL + * + * \param dst The destination buffer + * \param src The source data to be encoded + * \param srclen The number of bytes present in the source buffer + * \param max The maximum number of bytes to write into the destination + * buffer, including the terminating NULL character + */ +int ast_base64url_encode(char *dst, const unsigned char *src, int srclen, int max); + +/*! + * \brief Decode string from base64 URL + * + * \note The returned string will need to be freed later + * + * \param src The source buffer + * + * \retval NULL on failure + * \retval Decoded string on success + */ +char *ast_base64url_decode_string(const char *src); + +/*! + * \brief Encode string in base64 URL + * + * \note The returned string will need to be freed later + * + * \param src The source data to be encoded + * + * \retval NULL on failure + * \retval Encoded string on success + */ +char *ast_base64url_encode_string(const char *src); + #define AST_URI_ALPHANUM (1 << 0) #define AST_URI_MARK (1 << 1) #define AST_URI_UNRESERVED (AST_URI_ALPHANUM | AST_URI_MARK) diff --git a/main/utils.c b/main/utils.c index 827ee2e57aaec3fbb24285e92e7cb7f067007334..c6e71d9fd26f315e6ef41544aa6f94b97c79f290 100644 --- a/main/utils.c +++ b/main/utils.c @@ -70,8 +70,15 @@ #define AST_API_MODULE #include "asterisk/alertpipe.h" +/* These arrays are global static variables because they are only modified + * once - in base64_init. The only purpose they have is to serve as a dictionary + * for encoding and decoding base64 and base64 URL, so there's no harm in + * accessing these arrays in multiple threads. + */ static char base64[64]; +static char base64url[64]; static char b2a[256]; +static char b2a_url[256]; AST_THREADSTORAGE(inet_ntoa_buf); @@ -417,28 +424,150 @@ char *ast_base64encode_string(const char *src) return encoded_string; } +int ast_base64url_decode(unsigned char *dst, const char *src, int max) +{ + int cnt = 0; + unsigned int byte = 0; + unsigned int bits = 0; + + while (*src && (cnt < max)) { + byte <<= 6; + byte |= (b2a_url[(int)(*src)]) & 0x3f; + bits += 6; + src++; + if (bits >= 8) { + bits -= 8; + *dst = (byte >> bits) & 0xff; + dst++; + cnt++; + } + } + return cnt; +} + +char *ast_base64url_decode_string(const char *src) +{ + size_t decoded_len; + unsigned char *decoded_string; + + if (ast_strlen_zero(src)) { + return NULL; + } + + decoded_len = strlen(src) * 3 / 4; + decoded_string = ast_malloc(decoded_len + 1); + if (!decoded_string) { + return NULL; + } + + ast_base64url_decode(decoded_string, src, decoded_len); + decoded_string[decoded_len] = '\0'; + + return (char *)decoded_string; +} + +int ast_base64url_encode_full(char *dst, const unsigned char *src, int srclen, int max, int linebreaks) +{ + int cnt = 0; + int col = 0; + unsigned int byte = 0; + int bits = 0; + int cntin = 0; + + max--; + while ((cntin < srclen) && (cnt < max)) { + byte <<= 8; + byte |= *(src++); + bits += 8; + cntin++; + if ((bits == 24) && (cnt + 4 <= max)) { + *dst++ = base64url[(byte >> 18) & 0x3f]; + *dst++ = base64url[(byte >> 12) & 0x3f]; + *dst++ = base64url[(byte >> 6) & 0x3f]; + *dst++ = base64url[(byte) & 0x3f]; + cnt += 4; + col += 4; + bits = 0; + byte = 0; + } + if (linebreaks && (cnt < max) && (col == 64)) { + *dst++ = '\n'; + cnt++; + col = 0; + } + } + if (bits && (cnt + 4 <= max)) { + byte <<= 24 - bits; + *dst++ = base64url[(byte >> 18) & 0x3f]; + *dst++ = base64url[(byte >> 12) & 0x3f]; + if (bits == 16) { + *dst++ = base64url[(byte >> 6) & 0x3f]; + } + cnt += 4; + } + if (linebreaks && (cnt < max)) { + *dst++ = '\n'; + cnt++; + } + *dst = '\0'; + return cnt; +} + +int ast_base64url_encode(char *dst, const unsigned char *src, int srclen, int max) +{ + return ast_base64url_encode_full(dst, src, srclen, max, 0); +} + +char *ast_base64url_encode_string(const char *src) +{ + size_t encoded_len; + char *encoded_string; + + if (ast_strlen_zero(src)) { + return NULL; + } + + encoded_len = ((strlen(src) * 4 / 3 + 3) & ~3) + 1; + encoded_string = ast_malloc(encoded_len); + + ast_base64url_encode(encoded_string, (const unsigned char *)src, strlen(src), encoded_len); + + return encoded_string; +} + static void base64_init(void) { int x; memset(b2a, -1, sizeof(b2a)); + memset(b2a_url, -1, sizeof(b2a_url)); /* Initialize base-64 Conversion table */ for (x = 0; x < 26; x++) { /* A-Z */ base64[x] = 'A' + x; + base64url[x] = 'A' + x; b2a['A' + x] = x; + b2a_url['A' + x] = x; /* a-z */ base64[x + 26] = 'a' + x; + base64url[x + 26] = 'a' + x; b2a['a' + x] = x + 26; + b2a_url['a' + x] = x + 26; /* 0-9 */ if (x < 10) { base64[x + 52] = '0' + x; + base64url[x + 52] = '0' + x; b2a['0' + x] = x + 52; + b2a_url['0' + x] = x + 52; } } base64[62] = '+'; base64[63] = '/'; + base64url[62] = '-'; + base64url[63] = '_'; b2a[(int)'+'] = 62; b2a[(int)'/'] = 63; + b2a_url[(int)'-'] = 62; + b2a_url[(int)'_'] = 63; } const struct ast_flags ast_uri_http = {AST_URI_UNRESERVED}; diff --git a/res/res_pjsip_stir_shaken.c b/res/res_pjsip_stir_shaken.c index 351d7ccf29ca0f9ac77b232c61875a23feb474c3..a90b821e553e4feadfc5aeca7bccbb74ae2af483 100644 --- a/res/res_pjsip_stir_shaken.c +++ b/res/res_pjsip_stir_shaken.c @@ -146,14 +146,14 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r } encoded_val = strtok_r(identity_hdr_val, ".", &identity_hdr_val); - header = ast_base64decode_string(encoded_val); + header = ast_base64url_decode_string(encoded_val); if (ast_strlen_zero(header)) { ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED); return 0; } encoded_val = strtok_r(identity_hdr_val, ".", &identity_hdr_val); - payload = ast_base64decode_string(encoded_val); + payload = ast_base64url_decode_string(encoded_val); if (ast_strlen_zero(payload)) { ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED); return 0; @@ -241,7 +241,7 @@ static void add_identity_header(const struct ast_sip_session *session, pjsip_tx_ header = ast_json_object_get(json, "header"); dumped_string = ast_json_dump_string(header); - encoded_header = ast_base64encode_string(dumped_string); + encoded_header = ast_base64url_encode_string(dumped_string); ast_json_free(dumped_string); if (!encoded_header) { ast_log(LOG_ERROR, "Failed to encode STIR/SHAKEN header\n"); @@ -250,7 +250,7 @@ static void add_identity_header(const struct ast_sip_session *session, pjsip_tx_ payload = ast_json_object_get(json, "payload"); dumped_string = ast_json_dump_string(payload); - encoded_payload = ast_base64encode_string(dumped_string); + encoded_payload = ast_base64url_encode_string(dumped_string); ast_json_free(dumped_string); if (!encoded_payload) { ast_log(LOG_ERROR, "Failed to encode STIR/SHAKEN payload\n"); diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c index f8eb97fe4fd3617693e14f6e5cb5d837f16d4f3a..dbc2de08c47809c2ce269ff8d40ff1f92f0147e6 100644 --- a/res/res_stir_shaken.c +++ b/res/res_stir_shaken.c @@ -104,9 +104,6 @@ <configOption name="attestation"> <synopsis>Attestation level</synopsis> </configOption> - <configOption name="origid" default=""> - <synopsis>The origination ID</synopsis> - </configOption> <configOption name="caller_id_number" default=""> <synopsis>The caller ID number to match on.</synopsis> </configOption> @@ -503,7 +500,7 @@ static int stir_shaken_verify_signature(const char *msg, const char *signature, EVP_MD_CTX *mdctx = NULL; int ret = 0; unsigned char *decoded_signature; - size_t signature_length, decoded_signature_length, padding = 0; + size_t signature_length, decoded_signature_length; mdctx = EVP_MD_CTX_create(); if (!mdctx) { @@ -525,19 +522,12 @@ static int stir_shaken_verify_signature(const char *msg, const char *signature, return -1; } - /* We need to decode the signature from base64 to bytes. Make sure we have + /* We need to decode the signature from base64 URL to bytes. Make sure we have * at least enough characters for this check */ signature_length = strlen(signature); - if (signature_length > 2 && signature[signature_length - 1] == '=') { - padding++; - if (signature[signature_length - 2] == '=') { - padding++; - } - } - - decoded_signature_length = (signature_length / 4 * 3) - padding; + decoded_signature_length = (signature_length * 3 / 4); decoded_signature = ast_calloc(1, decoded_signature_length); - ast_base64decode(decoded_signature, signature, decoded_signature_length); + ast_base64url_decode(decoded_signature, signature, decoded_signature_length); ret = EVP_DigestVerifyFinal(mdctx, decoded_signature, decoded_signature_length); if (ret != 1) { @@ -944,7 +934,7 @@ static unsigned char *stir_shaken_sign(char *json_str, EVP_PKEY *private_key) goto cleanup; } - /* There are 6 bits to 1 base64 digit, so in order to get the size of the base64 encoded + /* There are 6 bits to 1 base64 URL digit, so in order to get the size of the base64 encoded * signature, we need to multiply by the number of bits in a byte and divide by 6. Since * there's rounding when doing base64 conversions, add 3 bytes, just in case, and account * for padding. Add another byte for the NULL-terminator. @@ -956,7 +946,7 @@ static unsigned char *stir_shaken_sign(char *json_str, EVP_PKEY *private_key) goto cleanup; } - ast_base64encode((char *)encoded_signature, signature, signature_length, encoded_length); + ast_base64url_encode((char *)encoded_signature, signature, signature_length, encoded_length); cleanup: if (mdctx) { @@ -1013,20 +1003,22 @@ static int stir_shaken_add_attest(struct ast_json *json, const char *attest) * \brief Adds the 'origid' field to the JWT. * * \param json The JWT - * \param origid The value to set origid to * * \retval 0 on success * \retval -1 on failure */ -static int stir_shaken_add_origid(struct ast_json *json, const char *origid) +static int stir_shaken_add_origid(struct ast_json *json) { struct ast_json *value; + char uuid_str[AST_UUID_STR_LEN]; - value = ast_json_string_create(origid); - if (!origid) { + ast_uuid_generate_str(uuid_str, sizeof(uuid_str)); + if (strlen(uuid_str) != (AST_UUID_STR_LEN - 1)) { return -1; } + value = ast_json_string_create(uuid_str); + return ast_json_object_set(ast_json_object_get(json, "payload"), "origid", value); } @@ -1097,7 +1089,7 @@ struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json) goto cleanup; } - if (stir_shaken_add_origid(json, stir_shaken_certificate_get_origid(cert))) { + if (stir_shaken_add_origid(json)) { ast_log(LOG_ERROR, "Failed to add 'origid' to payload\n"); goto cleanup; } diff --git a/res/res_stir_shaken/certificate.c b/res/res_stir_shaken/certificate.c index f4103f96ecf1821feefbb30e07c0021d351c136b..df4f38b8f76d57cfa1eaaaba3283fda973b739b2 100644 --- a/res/res_stir_shaken/certificate.c +++ b/res/res_stir_shaken/certificate.c @@ -40,8 +40,6 @@ struct stir_shaken_certificate { AST_STRING_FIELD(caller_id_number); /*! The attestation level for this certificate */ AST_STRING_FIELD(attestation); - /*! The origination ID for this certificate */ - AST_STRING_FIELD(origid); ); /*! The private key for the certificate */ EVP_PKEY *private_key; @@ -105,11 +103,6 @@ const char *stir_shaken_certificate_get_attestation(struct stir_shaken_certifica return cert ? cert->attestation : NULL; } -const char *stir_shaken_certificate_get_origid(struct stir_shaken_certificate *cert) -{ - return cert ? cert->origid : NULL; -} - EVP_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert) { return cert ? cert->private_key : NULL; @@ -378,7 +371,6 @@ int stir_shaken_certificate_load(void) on_load_public_cert_url, public_cert_url_to_str, NULL, 0, 0); ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "attestation", "", on_load_attestation, attestation_to_str, NULL, 0, 0); - ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "origid", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct stir_shaken_certificate, origid)); ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "caller_id_number", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct stir_shaken_certificate, caller_id_number)); ast_cli_register_multiple(stir_shaken_certificate_cli, diff --git a/res/res_stir_shaken/certificate.h b/res/res_stir_shaken/certificate.h index 9574d46795051cf1e2a4842574d6b5fa83902aaa..c95cba56b6f5494598e16b3cab10cfbcd0e6a897 100644 --- a/res/res_stir_shaken/certificate.h +++ b/res/res_stir_shaken/certificate.h @@ -54,16 +54,6 @@ const char *stir_shaken_certificate_get_public_cert_url(struct stir_shaken_certi */ const char *stir_shaken_certificate_get_attestation(struct stir_shaken_certificate *cert); -/*! - * \brief Get the origination ID associated with a certificate - * - * \param cert The certificate - * - * \retval NULL on failure - * \retval The origid on success - */ -const char *stir_shaken_certificate_get_origid(struct stir_shaken_certificate *cert); - /*! * \brief Get the private key associated with a certificate *