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
diff --git a/libchromeos.gypi b/libchromeos.gypi
index 44f0e9c..87dda53 100644
--- a/libchromeos.gypi
+++ b/libchromeos.gypi
@@ -42,6 +42,7 @@
           'dbus-c++-1',
           'dbus-glib-1',
           'glib-2.0',
+          'openssl',
         ],
         'deps': ['<@(exported_deps)'],
       },