[Asterisk-code-review] STIR/SHAKEN: Switch to base64 URL encoding. (asterisk[master])

Joshua Colp asteriskteam at digium.com
Wed May 12 06:42:59 CDT 2021


Joshua Colp has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/15814 )

Change subject: STIR/SHAKEN: Switch to base64 URL encoding.
......................................................................

STIR/SHAKEN: Switch to base64 URL encoding.

STIR/SHAKEN encodes using base64 URL format. Currently, we just use
base64. New functions have been added that convert to and from base64
encoding.

The origid field should also be an UUID. This means there's no reason to
have it as an option in stir_shaken.conf, as we can simply generate one
when creating the Identity header.

https://wiki.asterisk.org/wiki/display/AST/OpenSIPit+2021

Change-Id: Icf094a2a54e87db91d6b12244c9f5ba4fc2e0b8c
---
M configs/samples/stir_shaken.conf.sample
A doc/UPGRADE-staging/stir_shaken_origid.txt
M include/asterisk/utils.h
M main/utils.c
M res/res_pjsip_stir_shaken.c
M res/res_stir_shaken.c
M res/res_stir_shaken/certificate.c
M res/res_stir_shaken/certificate.h
8 files changed, 214 insertions(+), 46 deletions(-)

Approvals:
  George Joseph: Looks good to me, approved
  Joshua Colp: Approved for Submit



diff --git a/configs/samples/stir_shaken.conf.sample b/configs/samples/stir_shaken.conf.sample
index 1bd2606..c39bc97 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 0000000..f0b8977
--- /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 0ee11ee..08120bf 100644
--- a/include/asterisk/utils.h
+++ b/include/asterisk/utils.h
@@ -276,6 +276,66 @@
  */
 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 827ee2e..c6e71d9 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 @@
 	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 351d7cc..a90b821 100644
--- a/res/res_pjsip_stir_shaken.c
+++ b/res/res_pjsip_stir_shaken.c
@@ -146,14 +146,14 @@
 	}
 
 	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 @@
 
 	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 @@
 
 	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 f8eb97f..dbc2de0 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 @@
 	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 @@
 		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 @@
 		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 @@
 		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 @@
  * \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 @@
 		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 f4103f9..df4f38b 100644
--- a/res/res_stir_shaken/certificate.c
+++ b/res/res_stir_shaken/certificate.c
@@ -40,8 +40,6 @@
 		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 @@
 	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 @@
 		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 9574d46..c95cba5 100644
--- a/res/res_stir_shaken/certificate.h
+++ b/res/res_stir_shaken/certificate.h
@@ -55,16 +55,6 @@
 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
  *
  * \param cert The certificate to get the private key from

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/15814
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: Icf094a2a54e87db91d6b12244c9f5ba4fc2e0b8c
Gerrit-Change-Number: 15814
Gerrit-PatchSet: 6
Gerrit-Owner: Benjamin Keith Ford <bford at digium.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at sangoma.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20210512/97d64010/attachment-0001.html>


More information about the asterisk-code-review mailing list