;
; Must have an attestation of A, B, or C
;attestation=C
-;
-; The origination identifier for the certificate
-;origid=MyAsterisk
--- /dev/null
+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.
*/
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)
#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);
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};
}
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;
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");
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");
<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>
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) {
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) {
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.
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) {
* \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);
}
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;
}
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;
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;
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,
*/
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
*