Test AES mode wrappers.

AES_ctr128_encrypt, in particular, has a decent number of external
callers but is completely untested. I haven't included
AES_cfb128_encrypt because its EVP_CIPHER counterpart is tested in
decrept_test. But the EVP_CIPHER counterpart simply calls
AES_cfb128_encrypt, so it's tested transitively.

Change-Id: I0133dbd5b13c2b4045a89a04f29240008a279186
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/41425
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/cipher_extra/cipher_test.cc b/crypto/cipher_extra/cipher_test.cc
index 5ff308c..af7e0e7 100644
--- a/crypto/cipher_extra/cipher_test.cc
+++ b/crypto/cipher_extra/cipher_test.cc
@@ -61,8 +61,10 @@
 
 #include <gtest/gtest.h>
 
+#include <openssl/aes.h>
 #include <openssl/cipher.h>
 #include <openssl/err.h>
+#include <openssl/nid.h>
 #include <openssl/span.h>
 
 #include "../test/file_test.h"
@@ -221,6 +223,91 @@
         EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, tag.size(), rtag));
     EXPECT_EQ(Bytes(tag), Bytes(rtag, tag.size()));
   }
+
+  // Additionally test low-level AES mode APIs. Skip runs where |copy| because
+  // it does not apply.
+  if (!copy) {
+    int nid = EVP_CIPHER_nid(cipher);
+    bool is_ctr = nid == NID_aes_128_ctr || nid == NID_aes_192_ctr ||
+                  nid == NID_aes_256_ctr;
+    bool is_cbc = nid == NID_aes_128_cbc || nid == NID_aes_192_cbc ||
+                  nid == NID_aes_256_cbc;
+    bool is_ofb = nid == NID_aes_128_ofb128 || nid == NID_aes_192_ofb128 ||
+                  nid == NID_aes_256_ofb128;
+    if (is_ctr || is_cbc || is_ofb) {
+      AES_KEY aes;
+      if (encrypt || !is_cbc) {
+        ASSERT_EQ(0, AES_set_encrypt_key(key.data(), key.size() * 8, &aes));
+      } else {
+        ASSERT_EQ(0, AES_set_decrypt_key(key.data(), key.size() * 8, &aes));
+      }
+
+      // The low-level APIs all work in-place.
+      bssl::Span<const uint8_t> input = *in;
+      result.clear();
+      if (in_place) {
+        result = *in;
+        input = result;
+      } else {
+        result.resize(out->size());
+      }
+      bssl::Span<uint8_t> output = bssl::MakeSpan(result);
+      ASSERT_EQ(input.size(), output.size());
+
+      // The low-level APIs all use block-size IVs.
+      ASSERT_EQ(iv.size(), size_t{AES_BLOCK_SIZE});
+      uint8_t ivec[AES_BLOCK_SIZE];
+      OPENSSL_memcpy(ivec, iv.data(), iv.size());
+
+      if (is_ctr) {
+        unsigned num = 0;
+        uint8_t ecount_buf[AES_BLOCK_SIZE];
+        if (chunk_size == 0) {
+          AES_ctr128_encrypt(input.data(), output.data(), input.size(), &aes,
+                             ivec, ecount_buf, &num);
+        } else {
+          do {
+            size_t todo = std::min(input.size(), chunk_size);
+            AES_ctr128_encrypt(input.data(), output.data(), todo, &aes, ivec,
+                               ecount_buf, &num);
+            input = input.subspan(todo);
+            output = output.subspan(todo);
+          } while (!input.empty());
+        }
+        EXPECT_EQ(Bytes(*out), Bytes(result));
+      } else if (is_cbc && chunk_size % AES_BLOCK_SIZE == 0) {
+        // Note |AES_cbc_encrypt| requires block-aligned chunks.
+        if (chunk_size == 0) {
+          AES_cbc_encrypt(input.data(), output.data(), input.size(), &aes, ivec,
+                          encrypt);
+        } else {
+          do {
+            size_t todo = std::min(input.size(), chunk_size);
+            AES_cbc_encrypt(input.data(), output.data(), todo, &aes, ivec,
+                            encrypt);
+            input = input.subspan(todo);
+            output = output.subspan(todo);
+          } while (!input.empty());
+        }
+        EXPECT_EQ(Bytes(*out), Bytes(result));
+      } else if (is_ofb) {
+        int num = 0;
+        if (chunk_size == 0) {
+          AES_ofb128_encrypt(input.data(), output.data(), input.size(), &aes,
+                             ivec, &num);
+        } else {
+          do {
+            size_t todo = std::min(input.size(), chunk_size);
+            AES_ofb128_encrypt(input.data(), output.data(), todo, &aes, ivec,
+                               &num);
+            input = input.subspan(todo);
+            output = output.subspan(todo);
+          } while (!input.empty());
+        }
+        EXPECT_EQ(Bytes(*out), Bytes(result));
+      }
+    }
+  }
 }
 
 static void TestCipher(FileTest *t) {