From 013b205548368fc502d888820f345e64b442fa3b Mon Sep 17 00:00:00 2001 From: Parminder Singh <61920513+parmi93@users.noreply.github.com> Date: Fri, 16 Jun 2023 10:05:41 +0000 Subject: [PATCH] Changed encoding to Base64URL (no padding) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix - Changed encoding from Base64 (with padding) to Base64URL (no paddi ng) for data value 'vd' over SenML JSON format. According to the SenML JSON specification defined at RFC8428ยง4.3 the Base64 URL safe alphabet must be used (without padding) for Data Value 'vd': "(*) Data Value is a base64-encoded string with the URL-safe alphabet as defined in Section 5 of [RFC4648], with padding omitted. (In CBOR, the octets in the Data Value are encoded using a definite-length byte string, major type 2.)" So, encoding and decoding has been changed from Base64 (with padding) to Base64Url (without padding) for data value 'vd' over SenML JSON format. Note that the behavior of the code in data/json.c has not changed, data in JSON format will continue to be encoded in Base64 (with padding), see https://github.com/eclipse/leshan/issues/1444#issuecomment-1588852832 New tests has been added in order to test the Base64Url (without padding) encode and decode functions, and the previous tests have been modified by adding a new entry to be tested. Fix: #698 also see: github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues/553 --- core/internals.h | 6 +-- core/utils.c | 102 +++++++++++++++++++++++++---------- data/data.c | 4 +- data/json.c | 2 +- data/senml_json.c | 7 ++- tests/convert_numbers_test.c | 58 ++++++++++++++------ 6 files changed, 127 insertions(+), 52 deletions(-) diff --git a/core/internals.h b/core/internals.h index 35c926fbd..c1a560396 100644 --- a/core/internals.h +++ b/core/internals.h @@ -446,10 +446,10 @@ int utils_textToObjLink(const uint8_t * buffer, uint16_t * objectId, uint16_t * objectInstanceId); void utils_copyValue(void * dst, const void * src, size_t len); -size_t utils_base64GetSize(size_t dataLen); -size_t utils_base64Encode(const uint8_t * dataP, size_t dataLen, uint8_t * bufferP, size_t bufferLen); +size_t utils_base64GetSize(size_t dataLen, bool withPadding); +size_t utils_base64Encode(const uint8_t * dataP, size_t dataLen, uint8_t * bufferP, size_t bufferLen, bool useB64UrlAlphabet, bool withPadding); size_t utils_base64GetDecodedSize(const char * dataP, size_t dataLen); -size_t utils_base64Decode(const char * dataP, size_t dataLen, uint8_t * bufferP, size_t bufferLen); +size_t utils_base64Decode(const char * dataP, size_t dataLen, uint8_t * bufferP, size_t bufferLen, bool useB64UrlAlphabet); #ifdef LWM2M_CLIENT_MODE lwm2m_server_t * utils_findServer(lwm2m_context_t * contextP, void * fromSessionH); lwm2m_server_t * utils_findBootstrapServer(lwm2m_context_t * contextP, void * fromSessionH); diff --git a/core/utils.c b/core/utils.c index c30fd60be..6e9d30c56 100644 --- a/core/utils.c +++ b/core/utils.c @@ -960,38 +960,65 @@ static char b64Alphabet[64] = 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; +static char b64UrlAlphabet[64] = +{ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' +}; + static void prv_encodeBlock(const uint8_t input[3], - uint8_t output[4]) + uint8_t output[4], + const char *base64Alphabet) { - output[0] = b64Alphabet[input[0] >> 2]; - output[1] = b64Alphabet[((input[0] & 0x03) << 4) | (input[1] >> 4)]; - output[2] = b64Alphabet[((input[1] & 0x0F) << 2) | (input[2] >> 6)]; - output[3] = b64Alphabet[input[2] & 0x3F]; + output[0] = base64Alphabet[input[0] >> 2]; + output[1] = base64Alphabet[((input[0] & 0x03) << 4) | (input[1] >> 4)]; + output[2] = base64Alphabet[((input[1] & 0x0F) << 2) | (input[2] >> 6)]; + output[3] = base64Alphabet[input[2] & 0x3F]; } -size_t utils_base64GetSize(size_t dataLen) +size_t utils_base64GetSize(size_t dataLen, bool withPadding) { size_t result_len; - - result_len = 4 * (dataLen / 3); - if (dataLen % 3) result_len += 4; - + if(withPadding) + { + result_len = 4 * (dataLen / 3); + if (dataLen % 3) result_len += 4; + } + else + { + result_len = (dataLen * 4) / 3; + if(dataLen % 3) result_len += 1; + } return result_len; } size_t utils_base64Encode(const uint8_t * dataP, size_t dataLen, uint8_t * bufferP, - size_t bufferLen) + size_t bufferLen, + bool useB64UrlAlphabet, + bool withPadding) { unsigned int data_index; unsigned int result_index; size_t result_len; + char *base64Alphabet = NULL; - result_len = utils_base64GetSize(dataLen); + result_len = utils_base64GetSize(dataLen, withPadding); if (result_len > bufferLen) return 0; + if(useB64UrlAlphabet) + { + base64Alphabet = b64UrlAlphabet; + } + else + { + base64Alphabet = b64Alphabet; + } + data_index = 0; result_index = 0; while (data_index < dataLen) @@ -1002,19 +1029,25 @@ size_t utils_base64Encode(const uint8_t * dataP, // should never happen break; case 1: - bufferP[result_index] = b64Alphabet[dataP[data_index] >> 2]; - bufferP[result_index + 1] = b64Alphabet[(dataP[data_index] & 0x03) << 4]; - bufferP[result_index + 2] = PRV_B64_PADDING; - bufferP[result_index + 3] = PRV_B64_PADDING; + bufferP[result_index] = base64Alphabet[dataP[data_index] >> 2]; + bufferP[result_index + 1] = base64Alphabet[(dataP[data_index] & 0x03) << 4]; + if(withPadding) + { + bufferP[result_index + 2] = PRV_B64_PADDING; + bufferP[result_index + 3] = PRV_B64_PADDING; + } break; case 2: - bufferP[result_index] = b64Alphabet[dataP[data_index] >> 2]; - bufferP[result_index + 1] = b64Alphabet[(dataP[data_index] & 0x03) << 4 | (dataP[data_index + 1] >> 4)]; - bufferP[result_index + 2] = b64Alphabet[(dataP[data_index + 1] & 0x0F) << 2]; - bufferP[result_index + 3] = PRV_B64_PADDING; + bufferP[result_index] = base64Alphabet[dataP[data_index] >> 2]; + bufferP[result_index + 1] = base64Alphabet[(dataP[data_index] & 0x03) << 4 | (dataP[data_index + 1] >> 4)]; + bufferP[result_index + 2] = base64Alphabet[(dataP[data_index + 1] & 0x0F) << 2]; + if(withPadding) + { + bufferP[result_index + 3] = PRV_B64_PADDING; + } break; default: - prv_encodeBlock(dataP + data_index, bufferP + result_index); + prv_encodeBlock(dataP + data_index, bufferP + result_index, base64Alphabet); break; } data_index += 3; @@ -1055,18 +1088,29 @@ size_t utils_base64GetDecodedSize(const char * dataP, size_t dataLen) return result; } -static uint8_t prv_base64Value(char digit) +static uint8_t prv_base64Value(char digit, bool useB64UrlAlphabet) { uint8_t result = 0xFF; if (digit >= 'A' && digit <= 'Z') result = digit - 'A'; else if (digit >= 'a' && digit <= 'z') result = digit - 'a' + 26; else if (digit >= '0' && digit <= '9') result = digit - '0' + 52; - else if (digit == '+') result = 62; - else if (digit == '/') result = 63; + else + { + if(useB64UrlAlphabet) + { + if (digit == '-') result = 62; + else if (digit == '_') result = 63; + } + else + { + if (digit == '+') result = 62; + else if (digit == '/') result = 63; + } + } return result; } -size_t utils_base64Decode(const char * dataP, size_t dataLen, uint8_t * bufferP, size_t bufferLen) +size_t utils_base64Decode(const char * dataP, size_t dataLen, uint8_t * bufferP, size_t bufferLen, bool useB64UrlAlphabet) { size_t dataIndex; size_t bufferIndex; @@ -1080,23 +1124,23 @@ size_t utils_base64Decode(const char * dataP, size_t dataLen, uint8_t * bufferP, { uint8_t v1, v2, v3, v4; if (dataLen - dataIndex < 2) return 0; - v1 = prv_base64Value(dataP[dataIndex++]); + v1 = prv_base64Value(dataP[dataIndex++], useB64UrlAlphabet); if (v1 >= 64) return 0; - v2 = prv_base64Value(dataP[dataIndex++]); + v2 = prv_base64Value(dataP[dataIndex++], useB64UrlAlphabet); if (v2 >= 64) return 0; bufferP[bufferIndex++] = (v1 << 2) + (v2 >> 4); if (dataIndex < dataLen) { if (dataP[dataIndex] != PRV_B64_PADDING) { - v3 = prv_base64Value(dataP[dataIndex++]); + v3 = prv_base64Value(dataP[dataIndex++], useB64UrlAlphabet); if (v3 >= 64) return 0; bufferP[bufferIndex++] = (v2 << 4) + (v3 >> 2); if (dataIndex < dataLen) { if (dataP[dataIndex] != PRV_B64_PADDING) { - v4 = prv_base64Value(dataP[dataIndex++]); + v4 = prv_base64Value(dataP[dataIndex++], useB64UrlAlphabet); if (v4 >= 64) return 0; bufferP[bufferIndex++] = (v3 << 6) + v4; } diff --git a/data/data.c b/data/data.c index 814794345..886877f0b 100644 --- a/data/data.c +++ b/data/data.c @@ -119,10 +119,10 @@ static int prv_textSerialize(lwm2m_data_t * dataP, { size_t length; - length = utils_base64GetSize(dataP->value.asBuffer.length); + length = utils_base64GetSize(dataP->value.asBuffer.length, false); *bufferP = (uint8_t *)lwm2m_malloc(length); if (*bufferP == NULL) return 0; - length = utils_base64Encode(dataP->value.asBuffer.buffer, dataP->value.asBuffer.length, *bufferP, length); + length = utils_base64Encode(dataP->value.asBuffer.buffer, dataP->value.asBuffer.length, *bufferP, length, true, false); if (length == 0) { lwm2m_free(*bufferP); diff --git a/data/json.c b/data/json.c index 0d9e39075..eaac5e23c 100644 --- a/data/json.c +++ b/data/json.c @@ -867,7 +867,7 @@ static int prv_serializeValue(lwm2m_data_t * tlvP, memcpy(buffer, JSON_ITEM_STRING_BEGIN, JSON_ITEM_STRING_BEGIN_SIZE); head = JSON_ITEM_STRING_BEGIN_SIZE; - res = utils_base64Encode(tlvP->value.asBuffer.buffer, tlvP->value.asBuffer.length, buffer+head, bufferLen - head); + res = utils_base64Encode(tlvP->value.asBuffer.buffer, tlvP->value.asBuffer.length, buffer+head, bufferLen - head, false, true); if (tlvP->value.asBuffer.length != 0 && res == 0) return -1; head += res; diff --git a/data/senml_json.c b/data/senml_json.c index bfe23b6d6..78de8fd2f 100644 --- a/data/senml_json.c +++ b/data/senml_json.c @@ -481,7 +481,8 @@ static bool prv_convertValue(const _record_t * recordP, dataLength = utils_base64Decode((const char *)recordP->value.value.asBuffer.buffer, recordP->value.value.asBuffer.length, data, - dataLength); + dataLength, + true); if (dataLength) { lwm2m_data_encode_opaque(data, dataLength, targetP); @@ -902,7 +903,9 @@ static int prv_serializeValue(const lwm2m_data_t * tlvP, res = utils_base64Encode(tlvP->value.asBuffer.buffer, tlvP->value.asBuffer.length, buffer+head, - bufferLen - head); + bufferLen - head, + true, + false); if (res < tlvP->value.asBuffer.length) return -1; head += res; } diff --git a/tests/convert_numbers_test.c b/tests/convert_numbers_test.c index 4fd2d43f6..2639dbc0e 100644 --- a/tests/convert_numbers_test.c +++ b/tests/convert_numbers_test.c @@ -419,29 +419,55 @@ static void test_utils_objLinkToText(void) CU_ASSERT_EQUAL(utils_objLinkToText(0, 0, text, 3), 3) } -static void test_utils_base64_1(const uint8_t *binary, size_t binaryLength, const char *base64) +static void test_utils_base64WithPadding_1(const uint8_t *binary, size_t binaryLength, const char *base64WithPadding) { - uint8_t encodeBuffer[8]; - uint8_t decodeBuffer[6]; - size_t base64Length = strlen(base64); + uint8_t encodeBuffer[12]; + uint8_t decodeBuffer[7]; + size_t base64WithPaddingLength = strlen(base64WithPadding); memset(encodeBuffer, 0, sizeof(encodeBuffer)); memset(decodeBuffer, 0xFF, sizeof(decodeBuffer)); - CU_ASSERT_EQUAL(utils_base64GetSize(binaryLength), base64Length) - CU_ASSERT_EQUAL(utils_base64GetDecodedSize(base64, base64Length), binaryLength) - CU_ASSERT_EQUAL(utils_base64Encode(binary, binaryLength, encodeBuffer, sizeof(encodeBuffer)), base64Length) - CU_ASSERT_NSTRING_EQUAL(encodeBuffer, base64, base64Length) - CU_ASSERT_EQUAL(utils_base64Decode(base64, base64Length, decodeBuffer, sizeof(decodeBuffer)), binaryLength) + CU_ASSERT_EQUAL(utils_base64GetSize(binaryLength, true), base64WithPaddingLength) + CU_ASSERT_EQUAL(utils_base64GetDecodedSize(base64WithPadding, base64WithPaddingLength), binaryLength) + CU_ASSERT_EQUAL(utils_base64Encode(binary, binaryLength, encodeBuffer, sizeof(encodeBuffer), false, true), base64WithPaddingLength) + CU_ASSERT_NSTRING_EQUAL(encodeBuffer, base64WithPadding, base64WithPaddingLength) + CU_ASSERT_EQUAL(utils_base64Decode(base64WithPadding, base64WithPaddingLength, decodeBuffer, sizeof(decodeBuffer), false), binaryLength) CU_ASSERT_EQUAL(memcmp(decodeBuffer, binary, binaryLength), 0) } -static void test_utils_base64(void) +static void test_utils_base64UrlNoPadding_1(const uint8_t *binary, size_t binaryLength, const char *base64UrlNoPadding) { - uint8_t binary[] = { 0, 1, 2, 3, 4, 5 }; - const char * base64[] = { "AA==", "AAE=", "AAEC", "AAECAw==", "AAECAwQ=", "AAECAwQF" }; + uint8_t encodeBuffer[10]; + uint8_t decodeBuffer[7]; + size_t base64UrlNoPaddingLength = strlen(base64UrlNoPadding); + memset(encodeBuffer, 0, sizeof(encodeBuffer)); + memset(decodeBuffer, 0xFF, sizeof(decodeBuffer)); + CU_ASSERT_EQUAL(utils_base64GetSize(binaryLength, false), base64UrlNoPaddingLength) + CU_ASSERT_EQUAL(utils_base64GetDecodedSize(base64UrlNoPadding, base64UrlNoPaddingLength), binaryLength) + CU_ASSERT_EQUAL(utils_base64Encode(binary, binaryLength, encodeBuffer, sizeof(encodeBuffer), true, false), base64UrlNoPaddingLength) + CU_ASSERT_NSTRING_EQUAL(encodeBuffer, base64UrlNoPadding, base64UrlNoPaddingLength) + CU_ASSERT_EQUAL(utils_base64Decode(base64UrlNoPadding, base64UrlNoPaddingLength, decodeBuffer, sizeof(decodeBuffer), true), binaryLength) + CU_ASSERT_EQUAL(memcmp(decodeBuffer, binary, binaryLength), 0) +} + +static void test_utils_base64WithPadding(void) +{ + uint8_t binary[] = { 0, 1, 2, 3, 4, 5, 0xFF }; + const char * base64WithPadding[] = { "AA==", "AAE=", "AAEC", "AAECAw==", "AAECAwQ=", "AAECAwQF", "AAECAwQF/w==" }; + size_t i; + for (i = 0; i < sizeof(binary); i++) + { + test_utils_base64WithPadding_1(binary, i+1, base64WithPadding[i]); + } +} + +static void test_utils_base64UrlNoPadding(void) +{ + uint8_t binary[] = { 0, 1, 2, 3, 4, 5, 0xFF }; + const char * base64UrlNoPadding[] = { "AA", "AAE", "AAEC", "AAECAw", "AAECAwQ", "AAECAwQF", "AAECAwQF_w" }; size_t i; for (i = 0; i < sizeof(binary); i++) { - test_utils_base64_1(binary, i+1, base64[i]); + test_utils_base64UrlNoPadding_1(binary, i+1, base64UrlNoPadding[i]); } } @@ -458,11 +484,13 @@ static struct TestTable table[] = { { "test of utils_floatToText()", test_utils_floatToText }, { "test of utils_floatToText(exponential)", test_utils_floatToTextExponential }, { "test of utils_objLinkToText()", test_utils_objLinkToText }, - { "test of base64 functions", test_utils_base64 }, + { "test of base64 (with padding) functions", test_utils_base64WithPadding }, + { "test of base64Url (without padding) functions", test_utils_base64UrlNoPadding }, { NULL, NULL }, }; -CU_ErrorCode create_convert_numbers_suit(void) { +CU_ErrorCode create_convert_numbers_suit(void) +{ CU_pSuite pSuite = NULL; pSuite = CU_add_suite("Suite_ConvertNumbers", NULL, NULL);