Make crypto::HMAC and crypto::Encryptor work with base::span

Added so
https://chromium-review.googlesource.com/c/chromium/src/+/2157285 can
avoid some unnecessary conversions to and from strings.

crypto::HMAC is particularly silly in its current form because Verify
takes char as input but Sign writes to unsigned char.

Bug: 559302
Change-Id: If2bb6c5bff2dfd8a1efd89088001af5c5458cb6b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2246935
Reviewed-by: Ryan Sleevi <[email protected]>
Reviewed-by: Josh Nohle <[email protected]>
Commit-Queue: David Benjamin <[email protected]>
Cr-Commit-Position: refs/heads/master@{#779038}
diff --git a/crypto/encryptor.cc b/crypto/encryptor.cc
index a58522c..9d4f655 100644
--- a/crypto/encryptor.cc
+++ b/crypto/encryptor.cc
@@ -49,8 +49,8 @@
 
 /////////////////////////////////////////////////////////////////////////////
 // Encyptor::Counter Implementation.
-Encryptor::Counter::Counter(base::StringPiece counter) {
-  CHECK(sizeof(counter_) == counter.length());
+Encryptor::Counter::Counter(base::span<const uint8_t> counter) {
+  CHECK(sizeof(counter_) == counter.size());
 
   memcpy(&counter_, counter.data(), sizeof(counter_));
 }
@@ -89,6 +89,12 @@
 Encryptor::~Encryptor() = default;
 
 bool Encryptor::Init(const SymmetricKey* key, Mode mode, base::StringPiece iv) {
+  return Init(key, mode, base::as_bytes(base::make_span(iv)));
+}
+
+bool Encryptor::Init(const SymmetricKey* key,
+                     Mode mode,
+                     base::span<const uint8_t> iv) {
   DCHECK(key);
   DCHECK(mode == CBC || mode == CTR);
 
@@ -101,120 +107,155 @@
 
   key_ = key;
   mode_ = mode;
-  iv_ = std::string(iv);
+  iv_.assign(iv.begin(), iv.end());
   return true;
 }
 
 bool Encryptor::Encrypt(base::StringPiece plaintext, std::string* ciphertext) {
-  CHECK(!plaintext.empty() || (mode_ == CBC));
-  return (mode_ == CTR) ?
-      CryptCTR(true, plaintext, ciphertext) :
-      Crypt(true, plaintext, ciphertext);
+  CHECK(!plaintext.empty() || mode_ == CBC);
+  return CryptString(/*do_encrypt=*/true, plaintext, ciphertext);
+}
+
+bool Encryptor::Encrypt(base::span<const uint8_t> plaintext,
+                        std::vector<uint8_t>* ciphertext) {
+  CHECK(!plaintext.empty() || mode_ == CBC);
+  return CryptBytes(/*do_encrypt=*/true, plaintext, ciphertext);
 }
 
 bool Encryptor::Decrypt(base::StringPiece ciphertext, std::string* plaintext) {
   CHECK(!ciphertext.empty());
-  return (mode_ == CTR) ?
-      CryptCTR(false, ciphertext, plaintext) :
-      Crypt(false, ciphertext, plaintext);
+  return CryptString(/*do_encrypt=*/false, ciphertext, plaintext);
+}
+
+bool Encryptor::Decrypt(base::span<const uint8_t> ciphertext,
+                        std::vector<uint8_t>* plaintext) {
+  CHECK(!ciphertext.empty());
+  return CryptBytes(/*do_encrypt=*/false, ciphertext, plaintext);
 }
 
 bool Encryptor::SetCounter(base::StringPiece counter) {
+  return SetCounter(base::as_bytes(base::make_span(counter)));
+}
+
+bool Encryptor::SetCounter(base::span<const uint8_t> counter) {
   if (mode_ != CTR)
     return false;
-  if (counter.length() != 16u)
+  if (counter.size() != 16u)
     return false;
 
-  counter_.reset(new Counter(counter));
+  counter_ = std::make_unique<Counter>(counter);
   return true;
 }
 
-bool Encryptor::Crypt(bool do_encrypt,
-                      base::StringPiece input,
-                      std::string* output) {
-  DCHECK(key_);  // Must call Init() before En/De-crypt.
-  // Work on the result in a local variable, and then only transfer it to
-  // |output| on success to ensure no partial data is returned.
+bool Encryptor::CryptString(bool do_encrypt,
+                            base::StringPiece input,
+                            std::string* output) {
+  size_t out_size = MaxOutput(do_encrypt, input.size());
+  CHECK_GT(out_size + 1, out_size);  // Overflow
   std::string result;
-  output->clear();
+  uint8_t* out_ptr =
+      reinterpret_cast<uint8_t*>(base::WriteInto(&result, out_size + 1));
+
+  base::Optional<size_t> len =
+      (mode_ == CTR)
+          ? CryptCTR(do_encrypt, base::as_bytes(base::make_span(input)),
+                     base::make_span(out_ptr, out_size))
+          : Crypt(do_encrypt, base::as_bytes(base::make_span(input)),
+                  base::make_span(out_ptr, out_size));
+  if (!len)
+    return false;
+
+  result.resize(*len);
+  *output = std::move(result);
+  return true;
+}
+
+bool Encryptor::CryptBytes(bool do_encrypt,
+                           base::span<const uint8_t> input,
+                           std::vector<uint8_t>* output) {
+  std::vector<uint8_t> result(MaxOutput(do_encrypt, input.size()));
+  base::Optional<size_t> len = (mode_ == CTR)
+                                   ? CryptCTR(do_encrypt, input, result)
+                                   : Crypt(do_encrypt, input, result);
+  if (!len)
+    return false;
+
+  result.resize(*len);
+  *output = std::move(result);
+  return true;
+}
+
+size_t Encryptor::MaxOutput(bool do_encrypt, size_t length) {
+  size_t result = length + ((do_encrypt && mode_ == CBC) ? 16 : 0);
+  CHECK_GE(result, length);  // Overflow
+  return result;
+}
+
+base::Optional<size_t> Encryptor::Crypt(bool do_encrypt,
+                                        base::span<const uint8_t> input,
+                                        base::span<uint8_t> output) {
+  DCHECK(key_);  // Must call Init() before En/De-crypt.
 
   const EVP_CIPHER* cipher = GetCipherForKey(key_);
   DCHECK(cipher);  // Already handled in Init();
 
   const std::string& key = key_->key();
-  DCHECK_EQ(EVP_CIPHER_iv_length(cipher), iv_.length());
-  DCHECK_EQ(EVP_CIPHER_key_length(cipher), key.length());
+  DCHECK_EQ(EVP_CIPHER_iv_length(cipher), iv_.size());
+  DCHECK_EQ(EVP_CIPHER_key_length(cipher), key.size());
 
   ScopedCipherCTX ctx;
   if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr,
                          reinterpret_cast<const uint8_t*>(key.data()),
-                         reinterpret_cast<const uint8_t*>(iv_.data()),
-                         do_encrypt))
-    return false;
+                         iv_.data(), do_encrypt)) {
+    return base::nullopt;
+  }
 
-  // When encrypting, add another block size of space to allow for any padding.
-  const size_t output_size = input.size() + (do_encrypt ? iv_.size() : 0);
-  CHECK_GT(output_size, 0u);
-  CHECK_GT(output_size + 1, input.size());
-  uint8_t* out_ptr =
-      reinterpret_cast<uint8_t*>(base::WriteInto(&result, output_size + 1));
+  // Encrypting needs a block size of space to allow for any padding.
+  CHECK_GE(output.size(), input.size() + (do_encrypt ? iv_.size() : 0));
   int out_len;
-  if (!EVP_CipherUpdate(ctx.get(), out_ptr, &out_len,
-                        reinterpret_cast<const uint8_t*>(input.data()),
-                        input.length()))
-    return false;
+  if (!EVP_CipherUpdate(ctx.get(), output.data(), &out_len, input.data(),
+                        input.size()))
+    return base::nullopt;
 
   // Write out the final block plus padding (if any) to the end of the data
   // just written.
   int tail_len;
-  if (!EVP_CipherFinal_ex(ctx.get(), out_ptr + out_len, &tail_len))
-    return false;
+  if (!EVP_CipherFinal_ex(ctx.get(), output.data() + out_len, &tail_len))
+    return base::nullopt;
 
   out_len += tail_len;
-  DCHECK_LE(out_len, static_cast<int>(output_size));
-  result.resize(out_len);
-
-  output->swap(result);
-  return true;
+  DCHECK_LE(out_len, static_cast<int>(output.size()));
+  return out_len;
 }
 
-bool Encryptor::CryptCTR(bool do_encrypt,
-                         base::StringPiece input,
-                         std::string* output) {
+base::Optional<size_t> Encryptor::CryptCTR(bool do_encrypt,
+                                           base::span<const uint8_t> input,
+                                           base::span<uint8_t> output) {
   if (!counter_.get()) {
     LOG(ERROR) << "Counter value not set in CTR mode.";
-    return false;
+    return base::nullopt;
   }
 
   AES_KEY aes_key;
   if (AES_set_encrypt_key(reinterpret_cast<const uint8_t*>(key_->key().data()),
                           key_->key().size() * 8, &aes_key) != 0) {
-    return false;
+    return base::nullopt;
   }
 
-  const size_t out_size = input.size();
-  CHECK_GT(out_size, 0u);
-  CHECK_GT(out_size + 1, input.size());
-
-  std::string result;
-  uint8_t* out_ptr =
-      reinterpret_cast<uint8_t*>(base::WriteInto(&result, out_size + 1));
-
   uint8_t ivec[AES_BLOCK_SIZE] = { 0 };
   uint8_t ecount_buf[AES_BLOCK_SIZE] = { 0 };
   unsigned int block_offset = 0;
 
   counter_->Write(ivec);
 
-  AES_ctr128_encrypt(reinterpret_cast<const uint8_t*>(input.data()), out_ptr,
-                     input.size(), &aes_key, ivec, ecount_buf, &block_offset);
+  // |output| must have room for |input|.
+  CHECK_GE(output.size(), input.size());
+  AES_ctr128_encrypt(input.data(), output.data(), input.size(), &aes_key, ivec,
+                     ecount_buf, &block_offset);
 
   // AES_ctr128_encrypt() updates |ivec|. Update the |counter_| here.
-  SetCounter(base::StringPiece(reinterpret_cast<const char*>(ivec),
-                               AES_BLOCK_SIZE));
-
-  output->swap(result);
-  return true;
+  SetCounter(ivec);
+  return input.size();
 }
 
 }  // namespace crypto