diff --git a/libraries/libfc/include/fc/crypto/base64.hpp b/libraries/libfc/include/fc/crypto/base64.hpp index d57e2d11e5..9559214df1 100644 --- a/libraries/libfc/include/fc/crypto/base64.hpp +++ b/libraries/libfc/include/fc/crypto/base64.hpp @@ -1,15 +1,14 @@ #pragma once #include -#include namespace fc { std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len); inline std::string base64_encode(char const* bytes_to_encode, unsigned int in_len) { return base64_encode( (unsigned char const*)bytes_to_encode, in_len); } -std::string base64_encode( const std::string_view& enc ); -std::string base64_decode( const std::string_view& encoded_string); +std::string base64_encode( const std::string& enc ); +std::string base64_decode( const std::string& encoded_string); std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len); inline std::string base64url_encode(char const* bytes_to_encode, unsigned int in_len) { return base64url_encode( (unsigned char const*)bytes_to_encode, in_len); } -std::string base64url_encode( const std::string_view& enc ); -std::string base64url_decode( const std::string_view& encoded_string); +std::string base64url_encode( const std::string& enc ); +std::string base64url_decode( const std::string& encoded_string); } // namespace fc diff --git a/libraries/libfc/src/crypto/base64.cpp b/libraries/libfc/src/crypto/base64.cpp index d5369dcbad..5ff4e68290 100644 --- a/libraries/libfc/src/crypto/base64.cpp +++ b/libraries/libfc/src/crypto/base64.cpp @@ -1,14 +1,10 @@ -/* +#include +#include +#include +/* base64.cpp and base64.h - base64 encoding and decoding with C++. - More information at - https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp - https://github.com/ReneNyffenegger/cpp-base64 - - Version: 2.rc.09 (release candidate) - - Copyright (C) 2004-2017, 2020-2022 René Nyffenegger + Copyright (C) 2004-2008 René Nyffenegger This source code is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages @@ -32,332 +28,134 @@ */ -#include -#include - -#include -#include -#if __cplusplus >= 201703L -#include -#endif // __cplusplus >= 201703L - namespace fc { -// base64.hpp -// Added template return type - -std::string base64_encode (std::string const& s, bool url = false); -std::string base64_encode_pem (std::string const& s); -std::string base64_encode_mime(std::string const& s); - -std::string base64_decode(std::string const& s, bool remove_linebreaks = false); -std::string base64_encode(unsigned char const*, size_t len, bool url = false); - -#if __cplusplus >= 201703L -// -// Interface with std::string_view rather than const std::string& -// Requires C++17 -// Provided by Yannic Bonenberger (https://github.com/Yannic) -// -std::string base64_encode (std::string_view s, bool url = false); -std::string base64_encode_pem (std::string_view s); -std::string base64_encode_mime(std::string_view s); - -std::string base64_decode(std::string_view s, bool remove_linebreaks = false); -#endif // __cplusplus >= 201703L - -// base64.cpp - -// Includes performance improvement from unmerged PR: https://github.com/ReneNyffenegger/cpp-base64/pull/27 - - // - // Depending on the url parameter in base64_chars, one of - // two sets of base64 characters needs to be chosen. - // They differ in their last two characters. - // -static const char* base64_chars[2] = { - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "+/", - - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "-_"}; - -static const unsigned char from_base64_chars[256] = { - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 62, 64, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, - 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 63, - 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 -}; - -static unsigned int pos_of_char(const unsigned char chr) { - // - // Return the position of chr within base64_encode() - // - - auto c = from_base64_chars[chr]; - if (c != 64) return c; - - // - // 2020-10-23: Throw std::exception rather than const char* - //(Pablo Martin-Gomez, https://github.com/Bouska) - // - // throw std::runtime_error("Input is not valid base64-encoded data."); - - // - // FC_ASSERT instead of throw runtime_error - // - FC_ASSERT(false, "encountered non-base64 character"); -} - -static std::string insert_linebreaks(std::string str, size_t distance) { - // - // Provided by https://github.com/JomaCorpFX, adapted by me. - // - if (!str.length()) { - return std::string{}; - } - - size_t pos = distance; +static constexpr char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; +static constexpr char base64url_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; - while (pos < str.size()) { - str.insert(pos, "\n"); - pos += distance + 1; - } - - return str; -} - -template -static std::string encode_with_line_breaks(String s) { - return insert_linebreaks(base64_encode(s, false), line_length); -} - -template -static std::string encode_pem(String s) { - return encode_with_line_breaks(s); -} - -template -static std::string encode_mime(String s) { - return encode_with_line_breaks(s); -} +static_assert(sizeof(base64_chars) == sizeof(base64url_chars), "base64 and base64url must have the same amount of chars"); -template -static std::string encode(String s, bool url) { - return base64_encode(reinterpret_cast(s.data()), s.length(), url); +static inline void throw_on_nonbase64(unsigned char c, const char* const b64_chars) { + FC_ASSERT(isalnum(c) || (c == b64_chars[sizeof(base64_chars)-3]) || (c == b64_chars[sizeof(base64_chars)-2]), "encountered non-base64 character"); } -std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) { - - size_t len_encoded = (in_len +2) / 3 * 4; - - unsigned char trailing_char = url ? '.' : '='; - - // - // Choose set of base64 characters. They differ - // for the last two positions, depending on the url - // parameter. - // A bool (as is the parameter url) is guaranteed - // to evaluate to either 0 or 1 in C++ therefore, - // the correct character set is chosen by subscripting - // base64_chars with url. - // - const char* base64_chars_ = base64_chars[url]; +std::string base64_encode_impl(unsigned char const* bytes_to_encode, unsigned int in_len, const char* const b64_chars) { - std::string ret; - ret.reserve(len_encoded); + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; - unsigned int pos = 0; + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; - while (pos < in_len) { - ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); - - if (pos+1 < in_len) { - ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); - - if (pos+2 < in_len) { - ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); - ret.push_back(base64_chars_[ bytes_to_encode[pos + 2] & 0x3f]); - } - else { - ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); - ret.push_back(trailing_char); - } - } - else { - - ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); - ret.push_back(trailing_char); - ret.push_back(trailing_char); - } - - pos += 3; + for(i = 0; (i <4) ; i++) + ret += b64_chars[char_array_4[i]]; + i = 0; } + } + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; - return ret; -} - -template -static std::string decode(String const& encoded_string, bool remove_linebreaks) { - // - // decode(…) is templated so that it can be used with String = const std::string& - // or std::string_view (requires at least C++17) - // - - if (encoded_string.empty()) return std::string{}; - - if (remove_linebreaks) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; - std::string copy(encoded_string); + for (j = 0; (j < i + 1); j++) + ret += b64_chars[char_array_4[j]]; - copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); + while((i++ < 3)) + ret += '='; - return base64_decode(copy, false); - } + } - size_t length_of_string = encoded_string.length(); - size_t pos = 0; - - // - // The approximate length (bytes) of the decoded string might be one or - // two bytes smaller, depending on the amount of trailing equal signs - // in the encoded string. This approximation is needed to reserve - // enough space in the string to be returned. - // - size_t approx_length_of_decoded_string = length_of_string / 4 * 3; - std::string ret; - ret.reserve(approx_length_of_decoded_string); - - while (pos < length_of_string) { - // - // Iterate over encoded input string in chunks. The size of all - // chunks except the last one is 4 bytes. - // - // The last chunk might be padded with equal signs or dots - // in order to make it 4 bytes in size as well, but this - // is not required as per RFC 2045. - // - // All chunks except the last one produce three output bytes. - // - // The last chunk produces at least one and up to three bytes. - // - - size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos+1) ); - - // - // Emit the first output byte that is produced in each chunk: - // - ret.push_back(static_cast( ( (pos_of_char(encoded_string.at(pos+0)) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4))); - - if ( ( pos + 2 < length_of_string ) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) - encoded_string.at(pos+2) != '=' && - encoded_string.at(pos+2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also. - ) - { - // - // Emit a chunk's second byte (which might not be produced in the last chunk). - // - unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos+2) ); - ret.push_back(static_cast( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2))); - - if ( ( pos + 3 < length_of_string ) && - encoded_string.at(pos+3) != '=' && - encoded_string.at(pos+3) != '.' - ) - { - // - // Emit a chunk's third byte (which might not be produced in the last chunk). - // - ret.push_back(static_cast( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string.at(pos+3)) )); - } - } - - pos += 4; - } + return ret; - return ret; } -std::string base64_decode(std::string const& s, bool remove_linebreaks) { - return decode(s, remove_linebreaks); +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + return base64_encode_impl(bytes_to_encode, in_len, base64_chars); } -std::string base64_encode(std::string const& s, bool url) { - return encode(s, url); +std::string base64_encode( const std::string& enc ) { + char const* s = enc.c_str(); + return base64_encode( (unsigned char const*)s, enc.size() ); } -std::string base64_encode_pem (std::string const& s) { - return encode_pem(s); +std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + return base64_encode_impl(bytes_to_encode, in_len, base64url_chars); } -std::string base64_encode_mime(std::string const& s) { - return encode_mime(s); +std::string base64url_encode( const std::string& enc ) { + char const* s = enc.c_str(); + return base64url_encode( (unsigned char const*)s, enc.size() ); } -#if __cplusplus >= 201703L -// -// Interface with std::string_view rather than const std::string& -// Requires C++17 -// Provided by Yannic Bonenberger (https://github.com/Yannic) -// +std::string base64_decode_impl(std::string const& encoded_string, const char* const b64_chars) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; -std::string base64_encode(std::string_view s, bool url) { - return encode(s, url); -} + while (in_len-- && encoded_string[in_] != '=') { + throw_on_nonbase64(encoded_string[in_], b64_chars); + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = strchr(b64_chars, char_array_4[i]) - b64_chars; -std::string base64_encode_pem(std::string_view s) { - return encode_pem(s); -} + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; -std::string base64_encode_mime(std::string_view s) { - return encode_mime(s); -} + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } -std::string base64_decode(std::string_view s, bool remove_linebreaks) { - return decode(s, remove_linebreaks); -} + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; -#endif // __cplusplus >= 201703L + for (j = 0; j <4; j++) + char_array_4[j] = strchr(b64_chars, char_array_4[j]) - b64_chars; -// end base64.cpp + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; -// fc interface + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } -std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - return base64_encode(bytes_to_encode, in_len, false); -} -std::string base64_encode(const std::string_view& enc) { - return base64_encode(enc, false); -} -std::string base64_decode(const std::string_view& encoded_string) { - return base64_decode(encoded_string, false); + return ret; } -std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - return base64_encode(bytes_to_encode, in_len, true); +std::string base64_decode(std::string const& encoded_string) { + return base64_decode_impl(encoded_string, base64_chars); } -std::string base64url_encode(const std::string_view& enc) { - return base64_encode(enc, true); -} -std::string base64url_decode(const std::string_view& encoded_string) { - return base64_decode(encoded_string, true); + +std::string base64url_decode(std::string const& encoded_string) { + return base64_decode_impl(encoded_string, base64url_chars); } } // namespace fc + diff --git a/libraries/libfc/test/test_base64.cpp b/libraries/libfc/test/test_base64.cpp index 38fb4e8cf8..ff6ac6a0ec 100644 --- a/libraries/libfc/test/test_base64.cpp +++ b/libraries/libfc/test/test_base64.cpp @@ -40,9 +40,7 @@ BOOST_AUTO_TEST_CASE(base64dec_extraequals) try { auto input = "YWJjMTIzJCYoKSc/tPUB+n5h========="s; auto expected_output = "abc123$&()'?\xb4\xf5\x01\xfa~a"s; - BOOST_CHECK_EXCEPTION(base64_decode(input), fc::exception, [](const fc::exception& e) { - return e.to_detail_string().find("encountered non-base64 character") != std::string::npos; - }); + BOOST_CHECK_EQUAL(expected_output, base64_decode(input)); } FC_LOG_AND_RETHROW(); BOOST_AUTO_TEST_CASE(base64dec_bad_stuff) try { @@ -53,134 +51,4 @@ BOOST_AUTO_TEST_CASE(base64dec_bad_stuff) try { }); } FC_LOG_AND_RETHROW(); -// tests from https://github.com/ReneNyffenegger/cpp-base64/blob/master/test.cpp -BOOST_AUTO_TEST_CASE(base64_cpp_base64_tests) try { - // - // Note: this file must be encoded in UTF-8 - // for the following test, otherwise, the test item - // fails. - // - const std::string orig = - "René Nyffenegger\n" - "http://www.renenyffenegger.ch\n" - "passion for data\n"; - - std::string encoded = base64_encode(reinterpret_cast(orig.c_str()), orig.length()); - std::string decoded = base64_decode(encoded); - - BOOST_CHECK_EQUAL(encoded, "UmVuw6kgTnlmZmVuZWdnZXIKaHR0cDovL3d3dy5yZW5lbnlmZmVuZWdnZXIuY2gKcGFzc2lvbiBmb3IgZGF0YQo="); - BOOST_CHECK_EQUAL(decoded, orig); - - // Test all possibilites of fill bytes (none, one =, two ==) - // References calculated with: https://www.base64encode.org/ - - std::string rest0_original = "abc"; - std::string rest0_reference = "YWJj"; - - std::string rest0_encoded = base64_encode(reinterpret_cast(rest0_original.c_str()), - rest0_original.length()); - std::string rest0_decoded = base64_decode(rest0_encoded); - - BOOST_CHECK_EQUAL(rest0_decoded, rest0_original); - BOOST_CHECK_EQUAL(rest0_reference, rest0_encoded); - - std::string rest1_original = "abcd"; - std::string rest1_reference = "YWJjZA=="; - - std::string rest1_encoded = base64_encode(reinterpret_cast(rest1_original.c_str()), - rest1_original.length()); - std::string rest1_decoded = base64_decode(rest1_encoded); - - BOOST_CHECK_EQUAL(rest1_decoded, rest1_original); - BOOST_CHECK_EQUAL(rest1_reference, rest1_encoded); - - std::string rest2_original = "abcde"; - std::string rest2_reference = "YWJjZGU="; - - std::string rest2_encoded = base64_encode(reinterpret_cast(rest2_original.c_str()), - rest2_original.length()); - std::string rest2_decoded = base64_decode(rest2_encoded); - - BOOST_CHECK_EQUAL(rest2_decoded, rest2_original); - BOOST_CHECK_EQUAL(rest2_reference, rest2_encoded); - - // -------------------------------------------------------------- - // - // Data that is 17 bytes long requires one padding byte when - // base-64 encoded. Such an encoded string could not correctly - // be decoded when encoded with «url semantics». This bug - // was discovered by https://github.com/kosniaz. The following - // test checks if this bug was fixed: - // - std::string a17_orig = "aaaaaaaaaaaaaaaaa"; - std::string a17_encoded = base64_encode(a17_orig); - std::string a17_encoded_url = base64url_encode(a17_orig); - - BOOST_CHECK_EQUAL(a17_encoded, "YWFhYWFhYWFhYWFhYWFhYWE="); - BOOST_CHECK_EQUAL(a17_encoded_url, "YWFhYWFhYWFhYWFhYWFhYWE."); - BOOST_CHECK_EQUAL(base64_decode(a17_encoded_url), a17_orig); - BOOST_CHECK_EQUAL(base64_decode(a17_encoded), a17_orig); - - // -------------------------------------------------------------- - - // characters 63 and 64 / URL encoding - - std::string s_6364 = "\x03" "\xef" "\xff" "\xf9"; - - std::string s_6364_encoded = base64_encode(s_6364); - std::string s_6364_encoded_url = base64url_encode(s_6364); - - BOOST_CHECK_EQUAL(s_6364_encoded, "A+//+Q=="); - BOOST_CHECK_EQUAL(s_6364_encoded_url, "A-__-Q.."); - BOOST_CHECK_EQUAL(base64_decode(s_6364_encoded), s_6364); - BOOST_CHECK_EQUAL(base64_decode(s_6364_encoded_url), s_6364); - - // ---------------------------------------------- - - std::string unpadded_input = "YWJjZGVmZw"; // Note the 'missing' "==" - std::string unpadded_decoded = base64_decode(unpadded_input); - BOOST_CHECK_EQUAL(unpadded_decoded, "abcdefg"); - - unpadded_input = "YWJjZGU"; // Note the 'missing' "=" - unpadded_decoded = base64_decode(unpadded_input); - BOOST_CHECK_EQUAL(unpadded_decoded, "abcde"); - - unpadded_input = ""; - unpadded_decoded = base64_decode(unpadded_input); - BOOST_CHECK_EQUAL(unpadded_decoded, ""); - - unpadded_input = "YQ"; - unpadded_decoded = base64_decode(unpadded_input); - BOOST_CHECK_EQUAL(unpadded_decoded, "a"); - - unpadded_input = "YWI"; - unpadded_decoded = base64_decode(unpadded_input); - BOOST_CHECK_EQUAL(unpadded_decoded, "ab"); - - // -------------------------------------------------------------- - // - // 2022-11-01 - // Replace - // encoded_string[…] with encoded_sring.at(…) - // in - // decode() - // - std::string not_null_terminated = std::string(1, 'a'); - BOOST_CHECK_THROW(base64_decode(not_null_terminated), std::out_of_range); - - // -------------------------------------------------------------- - // - // Test the string_view interface (which required C++17) - // - std::string_view sv_orig = "foobarbaz"; - std::string sv_encoded = base64_encode(sv_orig); - - BOOST_CHECK_EQUAL(sv_encoded, "Zm9vYmFyYmF6"); - - std::string sv_decoded = base64_decode(sv_encoded); - - BOOST_CHECK_EQUAL(sv_decoded, sv_orig); - -} FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file