libchromeos: Add Base64 encoding/decoding functions.
Several platform2 components require the ability to encode/decode
base64 data, and each have their own implementations. Add one to
libchromeos to consolidate usages.
BUG=chromium:435314
TEST=`FEATURES=test emerge-link libchromeos`
Change-Id: I9df752d8995773adb56fab34dd97626f3ddf1765
Reviewed-on: https://chromium-review.googlesource.com/247690
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/chromeos/data_encoding.cc b/chromeos/data_encoding.cc
index b3468b9..db02388 100644
--- a/chromeos/data_encoding.cc
+++ b/chromeos/data_encoding.cc
@@ -4,12 +4,18 @@
#include <chromeos/data_encoding.h>
+#include <memory>
+
+#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <chromeos/strings/string_utils.h>
-#include <cstring>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
namespace {
+using BIO_ptr_t = std::unique_ptr<BIO, void(*)(BIO*)>;
+
inline int HexToDec(int hex) {
int dec = -1;
if (hex >= '0' && hex <= '9') {
@@ -22,6 +28,19 @@
return dec;
}
+// Helper for Base64Encode() and Base64EncodeWrapLines().
+std::string Base64EncodeHelper(const void* data, size_t size, bool wrap_lines) {
+ BIO_ptr_t base64{BIO_new(BIO_f_base64()), BIO_free_all};
+ if (!wrap_lines)
+ BIO_set_flags(base64.get(), BIO_FLAGS_BASE64_NO_NL);
+ base64.reset(BIO_push(base64.release(), BIO_new(BIO_s_mem())));
+ CHECK_EQ(static_cast<int>(size), BIO_write(base64.get(), data, size));
+ CHECK_EQ(1, BIO_flush(base64.get()));
+ char* encoded_data = nullptr;
+ size_t encoded_data_size = BIO_get_mem_data(base64.get(), &encoded_data);
+ return std::string{encoded_data, encoded_data_size};
+}
+
} // namespace
/////////////////////////////////////////////////////////////////////////
@@ -96,5 +115,53 @@
return result;
}
+std::string Base64Encode(const void* data, size_t size) {
+ return Base64EncodeHelper(data, size, false);
+}
+
+std::string Base64EncodeWrapLines(const void* data, size_t size) {
+ return Base64EncodeHelper(data, size, true);
+}
+
+bool Base64Decode(const std::string& input, chromeos::Blob* output) {
+ BIO_ptr_t base64{BIO_new(BIO_f_base64()), BIO_free_all};
+ std::string temp_buffer;
+ const std::string* data = &input;
+ if (input.find_first_of("\r\n") != std::string::npos) {
+ // If we have line breaks in the string, make sure we have the ending one.
+ if (input.back() != '\n') {
+ temp_buffer = input;
+ temp_buffer.push_back('\n');
+ data = &temp_buffer;
+ }
+ } else {
+ // We have no line breaks. Tell BIO that...
+ BIO_set_flags(base64.get(), BIO_FLAGS_BASE64_NO_NL);
+ }
+ BIO* bmem = BIO_new_mem_buf(const_cast<char*>(data->data()), data->size());
+ if (!bmem) {
+ LOG(ERROR) << "Unable to get BIO buffer to decode base64 string";
+ return false;
+ }
+
+ base64.reset(BIO_push(base64.release(), bmem));
+ // base64 decoded data has 25% fewer bytes than the original (since every
+ // 3 source octets are encoded as 4 characters in base64).
+ // The upper estimate of the output data buffer is (encoded_size * 0.75),
+ // while the actual memory requirements could be a bit lower if the encoded
+ // data has any padding characters and/or line breaks.
+ output->resize(input.size() * 3 / 4);
+
+ int size_read = BIO_read(base64.get(), output->data(), output->size());
+ if (size_read < 0)
+ return false;
+ output->resize(size_read);
+
+ // If the source data had any invalid characters, BIO_read will return 0,
+ // so let's use this as an indication of error. Here we make an assumption
+ // that trying to decode an empty string is an error in itself.
+ return (size_read > 0);
+}
+
} // namespace data_encoding
} // namespace chromeos
diff --git a/chromeos/data_encoding.h b/chromeos/data_encoding.h
index 0b97ab7..4332c62 100644
--- a/chromeos/data_encoding.h
+++ b/chromeos/data_encoding.h
@@ -10,6 +10,7 @@
#include <vector>
#include <chromeos/chromeos_export.h>
+#include <chromeos/secure_blob.h>
namespace chromeos {
namespace data_encoding {
@@ -43,6 +44,40 @@
// content encoding.
CHROMEOS_EXPORT WebParamList WebParamsDecode(const std::string& data);
+// Encodes binary data using base64-encoding.
+CHROMEOS_EXPORT std::string Base64Encode(const void* data, size_t size);
+
+// Encodes binary data using base64-encoding and wraps lines at 64 character
+// boundary using LF as required by PEM (RFC 1421) specification.
+CHROMEOS_EXPORT std::string Base64EncodeWrapLines(const void* data,
+ size_t size);
+
+// Decodes the input string from Base64.
+CHROMEOS_EXPORT bool Base64Decode(const std::string& input,
+ chromeos::Blob* output);
+
+// Helper wrappers to use std::string and chromeos::Blob as binary data
+// containers.
+inline std::string Base64Encode(const chromeos::Blob& input) {
+ return Base64Encode(input.data(), input.size());
+}
+inline std::string Base64EncodeWrapLines(const chromeos::Blob& input) {
+ return Base64EncodeWrapLines(input.data(), input.size());
+}
+inline std::string Base64Encode(const std::string& input) {
+ return Base64Encode(input.data(), input.size());
+}
+inline std::string Base64EncodeWrapLines(const std::string& input) {
+ return Base64EncodeWrapLines(input.data(), input.size());
+}
+inline bool Base64Decode(const std::string& input, std::string* output) {
+ chromeos::Blob blob;
+ if (!Base64Decode(input, &blob))
+ return false;
+ *output = std::string{blob.begin(), blob.end()};
+ return true;
+}
+
} // namespace data_encoding
} // namespace chromeos
diff --git a/chromeos/data_encoding_unittest.cc b/chromeos/data_encoding_unittest.cc
index b7d6bdd..549c478 100644
--- a/chromeos/data_encoding_unittest.cc
+++ b/chromeos/data_encoding_unittest.cc
@@ -4,6 +4,8 @@
#include <chromeos/data_encoding.h>
+#include <algorithm>
+
#include <gtest/gtest.h>
namespace chromeos {
@@ -36,5 +38,109 @@
EXPECT_EQ("%", params[2].second);
}
+TEST(data_encoding, Base64Encode) {
+ const std::string text1 = "hello world";
+ const std::string encoded1 = "aGVsbG8gd29ybGQ=";
+
+ const std::string text2 =
+ "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque "
+ "molestie commodo. Viverra tincidunt integer erat ipsum, integer "
+ "molestie, arcu in, sit mauris ac a sed sit etiam.";
+ const std::string encoded2 =
+ "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBhbGlxdWF"
+ "tLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRpbmNpZHVudCBpbn"
+ "RlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFyY3UgaW4sIHNpdCBtYXVya"
+ "XMgYWMgYSBzZWQgc2l0IGV0aWFtLg==";
+
+ chromeos::Blob data3(256);
+ std::iota(data3.begin(), data3.end(), 0); // Fills the buffer with 0x00-0xFF.
+ const std::string encoded3 =
+ "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ"
+ "1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaW"
+ "prbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en"
+ "6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU"
+ "1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
+
+ EXPECT_EQ(encoded1, Base64Encode(text1));
+ EXPECT_EQ(encoded2, Base64Encode(text2));
+ EXPECT_EQ(encoded3, Base64Encode(data3));
+}
+
+TEST(data_encoding, Base64EncodeWrapLines) {
+ const std::string text1 = "hello world";
+ const std::string encoded1 = "aGVsbG8gd29ybGQ=\n";
+
+ const std::string text2 =
+ "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque "
+ "molestie commodo. Viverra tincidunt integer erat ipsum, integer "
+ "molestie, arcu in, sit mauris ac a sed sit etiam.";
+ const std::string encoded2 =
+ "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBh\n"
+ "bGlxdWFtLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRp\n"
+ "bmNpZHVudCBpbnRlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFy\n"
+ "Y3UgaW4sIHNpdCBtYXVyaXMgYWMgYSBzZWQgc2l0IGV0aWFtLg==\n";
+
+ chromeos::Blob data3(256);
+ std::iota(data3.begin(), data3.end(), 0); // Fills the buffer with 0x00-0xFF.
+ const std::string encoded3 =
+ "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\n"
+ "MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\n"
+ "YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\n"
+ "kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/\n"
+ "wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v\n"
+ "8PHy8/T19vf4+fr7/P3+/w==\n";
+
+ EXPECT_EQ(encoded1, Base64EncodeWrapLines(text1));
+ EXPECT_EQ(encoded2, Base64EncodeWrapLines(text2));
+ EXPECT_EQ(encoded3, Base64EncodeWrapLines(data3));
+}
+
+TEST(data_encoding, Base64Decode) {
+ const std::string encoded1 = "dGVzdCBzdHJpbmc=";
+
+ const std::string encoded2 =
+ "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGZhY2lsaXNpcyBlcmF0IG5lYyBh\n"
+ "bGlxdWFtLCBzY2VsZXJpc3F1ZSBtb2xlc3RpZSBjb21tb2RvLiBWaXZlcnJhIHRp\r\n"
+ "bmNpZHVudCBpbnRlZ2VyIGVyYXQgaXBzdW0sIGludGVnZXIgbW9sZXN0aWUsIGFy\r"
+ "Y3UgaW4sIHNpdCBtYXVyaXMgYWMgYSBzZWQgc2l0IGV0aWFt\n"
+ "Lg==\n\n\n";
+ const std::string decoded2 =
+ "Lorem ipsum dolor sit amet, facilisis erat nec aliquam, scelerisque "
+ "molestie commodo. Viverra tincidunt integer erat ipsum, integer "
+ "molestie, arcu in, sit mauris ac a sed sit etiam.";
+
+ const std::string encoded3 =
+ "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ"
+ "1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaW"
+ "prbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en"
+ "6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU"
+ "1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
+ chromeos::Blob decoded3(256);
+ std::iota(decoded3.begin(), decoded3.end(), 0); // Fill with 0x00..0xFF.
+
+ std::string decoded;
+ EXPECT_TRUE(Base64Decode(encoded1, &decoded));
+ EXPECT_EQ("test string", decoded);
+
+ EXPECT_TRUE(Base64Decode(encoded2, &decoded));
+ EXPECT_EQ(decoded2, decoded);
+
+ chromeos::Blob decoded_blob;
+ EXPECT_TRUE(Base64Decode(encoded3, &decoded_blob));
+ EXPECT_EQ(decoded3, decoded_blob);
+
+ EXPECT_FALSE(Base64Decode("A", &decoded_blob));
+ EXPECT_TRUE(decoded_blob.empty());
+
+ EXPECT_TRUE(Base64Decode("/w==", &decoded_blob));
+ EXPECT_EQ((chromeos::Blob{0xFF}), decoded_blob);
+
+ EXPECT_TRUE(Base64Decode("//8=", &decoded_blob));
+ EXPECT_EQ((chromeos::Blob{0xFF, 0xFF}), decoded_blob);
+
+ EXPECT_FALSE(Base64Decode("AAECAwQFB,cI", &decoded_blob));
+ EXPECT_TRUE(decoded_blob.empty());
+}
+
} // namespace data_encoding
} // namespace chromeos