EVP_AEAD: declare CRYPTO_IOVEC based APIs to support zero copy I/O.

Also provide some common helpers for ciphers to use.

Tests are included, however as no AEADs implement the new APIs yet, they
do nothing. The tests will become active one by one when individual
AEADs are implemented, and will be made fully active in
I3d68c6fbff18b5dcf40e509442ffbdfc5c34817f, after which internal support
for the legacy APIs can be removed.

Bug: 383343306
Change-Id: If96ab324516ce70c40f53f42d76c4e653eadcc94
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/84067
Reviewed-by: David Benjamin <davidben@google.com>
Auto-Submit: Rudolf Polzer <rpolzer@google.com>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Rudolf Polzer <rpolzer@google.com>
diff --git a/crypto/cipher/aead_test.cc b/crypto/cipher/aead_test.cc
index a84e67d..12a4819 100644
--- a/crypto/cipher/aead_test.cc
+++ b/crypto/cipher/aead_test.cc
@@ -48,6 +48,8 @@
 // one cannot assume that encrypting the same data will result in the same
 // ciphertext.
 constexpr uint32_t kNondeterministic = 1 << 7;
+// kVariableTag indicates that the AEAD outputs a variable length tag.
+constexpr uint32_t kVariableTag = 1 << 8;
 
 // RequiresADLength encodes an AD length requirement into flags.
 constexpr uint32_t RequiresADLength(size_t length) {
@@ -113,30 +115,30 @@
 
     {"AES_128_CBC_SHA1_TLS", EVP_aead_aes_128_cbc_sha1_tls,
      "aes_128_cbc_sha1_tls_tests.txt",
-     kLimitedImplementation | RequiresADLength(11)},
+     kLimitedImplementation | RequiresADLength(11) | kVariableTag},
 
     {"AES_128_CBC_SHA1_TLSImplicitIV",
      EVP_aead_aes_128_cbc_sha1_tls_implicit_iv,
      "aes_128_cbc_sha1_tls_implicit_iv_tests.txt",
-     kLimitedImplementation | RequiresADLength(11)},
+     kLimitedImplementation | RequiresADLength(11) | kVariableTag},
 
     {"AES_256_CBC_SHA1_TLS", EVP_aead_aes_256_cbc_sha1_tls,
      "aes_256_cbc_sha1_tls_tests.txt",
-     kLimitedImplementation | RequiresADLength(11)},
+     kLimitedImplementation | RequiresADLength(11) | kVariableTag},
 
     {"AES_256_CBC_SHA1_TLSImplicitIV",
      EVP_aead_aes_256_cbc_sha1_tls_implicit_iv,
      "aes_256_cbc_sha1_tls_implicit_iv_tests.txt",
-     kLimitedImplementation | RequiresADLength(11)},
+     kLimitedImplementation | RequiresADLength(11) | kVariableTag},
 
     {"DES_EDE3_CBC_SHA1_TLS", EVP_aead_des_ede3_cbc_sha1_tls,
      "des_ede3_cbc_sha1_tls_tests.txt",
-     kLimitedImplementation | RequiresADLength(11)},
+     kLimitedImplementation | RequiresADLength(11) | kVariableTag},
 
     {"DES_EDE3_CBC_SHA1_TLSImplicitIV",
      EVP_aead_des_ede3_cbc_sha1_tls_implicit_iv,
      "des_ede3_cbc_sha1_tls_implicit_iv_tests.txt",
-     kLimitedImplementation | RequiresADLength(11)},
+     kLimitedImplementation | RequiresADLength(11) | kVariableTag},
 
     {"AES_128_CTR_HMAC_SHA256", EVP_aead_aes_128_ctr_hmac_sha256,
      "aes_128_ctr_hmac_sha256.txt", kCanTruncateTags},
@@ -446,6 +448,421 @@
   });
 }
 
+class TestIOVecs {
+ public:
+  TestIOVecs() = default;
+
+  TestIOVecs(TestIOVecs &&) = default;
+  TestIOVecs &operator=(TestIOVecs &&) = default;
+
+  TestIOVecs(const TestIOVecs &other) { *this = other.Clone(); }
+
+  TestIOVecs &operator=(const TestIOVecs &other) {
+    return (*this = other.Clone());
+  }
+
+  static TestIOVecs Split(bssl::Span<const uint8_t> inp,
+                          std::vector<size_t> splits, bool in_place) {
+    TestIOVecs ret;
+    for (size_t i = 0; i <= splits.size(); i++) {
+      size_t start = i == 0 ? 0 : splits[i - 1];
+      size_t end = i == splits.size() ? inp.size() : splits[i];
+      assert(start <= end);
+      ret.Append(inp.subspan(start, end - start), in_place);
+    }
+    return ret;
+  }
+
+  bssl::Span<const CRYPTO_IOVEC> iovecs() const { return iovecs_; }
+
+  bssl::Span<const CRYPTO_IVEC> ivecs() const { return ivecs_; }
+
+  void Append(bssl::Span<const uint8_t> inp, bool in_place = false) {
+    CRYPTO_IOVEC iovec;
+    iovec.len = inp.size();
+    buffers_.emplace_back(inp.begin(), inp.end());
+    iovec.in = buffers_.back().data();
+    if (!in_place) {
+      buffers_.emplace_back(inp.size(), 'X');
+    }
+    iovec.out = buffers_.back().data();
+    iovecs_.push_back(iovec);
+
+    CRYPTO_IVEC ivec;
+    ivec.len = iovec.len;
+    ivec.in = iovec.in;
+    ivecs_.push_back(ivec);
+  }
+
+  std::vector<uint8_t> Output() const {
+    std::vector<uint8_t> ret;
+    for (const auto &iovec : iovecs_) {
+      ret.insert(ret.end(), iovec.out, iovec.out + iovec.len);
+    }
+    return ret;
+  }
+
+  TestIOVecs Clone() const {
+    TestIOVecs out;
+    for (const CRYPTO_IOVEC &iovec : iovecs_) {
+      out.Append(bssl::Span(iovec.in, iovec.len),
+                 /*in_place=*/iovec.in == iovec.out);
+    }
+    return out;
+  }
+
+ private:
+  std::vector<CRYPTO_IOVEC> iovecs_;
+  std::vector<CRYPTO_IVEC> ivecs_;
+  std::vector<std::vector<uint8_t>> buffers_;
+};
+
+std::vector<std::vector<size_t>> InterestingSplitsForLength(size_t length,
+                                                            size_t block_size) {
+  std::set<std::set<size_t>> ideas;  // Sort and dedup.
+
+  const size_t second_block_start = block_size;
+  const size_t unaligned_at_start = 1;
+  const size_t unaligned_at_end =
+      (length % block_size == 1) ? length - 2 : length - 1;
+  const size_t last_block_start = (length - 1) / block_size * block_size;
+
+  // 1 chunk.
+  ideas.insert({});
+
+  // 2 chunks.
+  ideas.insert({0});
+  ideas.insert({unaligned_at_start});
+  ideas.insert({second_block_start});
+  ideas.insert({last_block_start});
+  ideas.insert({unaligned_at_end});
+  ideas.insert({length});
+
+  // 3 chunks.
+
+  // Only one nonempty chunk.
+  ideas.insert({unaligned_at_start, unaligned_at_start});
+
+  // Try something useful.
+  ideas.insert({unaligned_at_start, last_block_start});
+  ideas.insert({second_block_start, last_block_start});
+  ideas.insert({unaligned_at_start, unaligned_at_end});
+  ideas.insert({second_block_start, unaligned_at_end});
+
+  // Convert to vector.
+  std::vector<std::vector<size_t>> out;
+  for (const auto &idea : ideas) {
+    // Skip anything out of range.
+    bool ok = true;
+    for (size_t pos : idea) {
+      if (pos > length) {
+        ok = false;
+        break;
+      }
+    }
+    if (!ok) {
+      continue;
+    }
+    out.push_back(std::vector<size_t>(idea.begin(), idea.end()));
+  }
+  return out;
+}
+
+std::string FormatSplits(const std::vector<size_t> &splits) {
+  std::ostringstream tracebuf;
+  for (size_t i = 0; i < splits.size(); ++i) {
+    if (i != 0) {
+      tracebuf << ", ";
+    }
+    tracebuf << splits[i];
+  }
+  return tracebuf.str();
+}
+
+void RunIOVecTests(const KnownAEAD &aead_config, bool in_place, bool detached) {
+  std::string test_vectors = "crypto/cipher/test/";
+  test_vectors += aead_config.test_vectors;
+  FileTestGTest(test_vectors.c_str(), [&](FileTest *t) {
+    std::vector<uint8_t> key, nonce, in, ad, ct, tag;
+    ASSERT_TRUE(t->GetBytes(&key, "KEY"));
+    ASSERT_TRUE(t->GetBytes(&nonce, "NONCE"));
+    ASSERT_TRUE(t->GetBytes(&in, "IN"));
+    ASSERT_TRUE(t->GetBytes(&ad, "AD"));
+    ASSERT_TRUE(t->GetBytes(&ct, "CT"));
+    ASSERT_TRUE(t->GetBytes(&tag, "TAG"));
+    size_t tag_len = tag.size();
+    if (t->HasAttribute("TAG_LEN")) {
+      // Legacy AEADs are MAC-then-encrypt and may include padding in the TAG
+      // field. TAG_LEN contains the actual size of the digest in that case.
+      std::string tag_len_str;
+      ASSERT_TRUE(t->GetAttribute(&tag_len_str, "TAG_LEN"));
+      tag_len = strtoul(tag_len_str.c_str(), nullptr, 10);
+      ASSERT_TRUE(tag_len);
+    }
+
+    for (const auto &adsplits :
+         InterestingSplitsForLength(ad.size(), /*block_size=*/16)) {
+      SCOPED_TRACE(FormatSplits(adsplits));
+      TestIOVecs advecs = TestIOVecs::Split(ad, {adsplits}, in_place);
+
+      bssl::ScopedEVP_AEAD_CTX ctx;
+
+      std::vector<uint8_t> out;
+      std::vector<uint8_t> out_tag(EVP_AEAD_max_overhead(aead_config.func()));
+      if (!t->HasAttribute("NO_SEAL") &&
+          !(aead_config.flags & kNondeterministic)) {
+        for (const auto &splits :
+             InterestingSplitsForLength(in.size(), /*block_size=*/16)) {
+          SCOPED_TRACE(FormatSplits(splits));
+          TestIOVecs iovecs = TestIOVecs::Split(in, splits, in_place);
+
+          ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
+              ctx.get(), aead_config.func(), key.data(), key.size(), tag_len,
+              evp_aead_seal));
+
+          size_t out_tag_len;
+          out_tag.resize(tag.size());
+          int ret = EVP_AEAD_CTX_sealv(
+              ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(),
+              out_tag.data(), &out_tag_len, out_tag.size(), nonce.data(),
+              nonce.size(), advecs.ivecs().data(), advecs.ivecs().size());
+
+          // Skip encryption for AEADs that don't implement sealv().
+          // TODO(crbug.com/383343306): Remove this check once all AEADs do.
+          if (!ret && ERR_equals(ERR_peek_error(), ERR_LIB_CIPHER,
+                                 CIPHER_R_CTRL_NOT_IMPLEMENTED)) {
+            t->SkipCurrent();
+            return;
+          }
+
+          ASSERT_TRUE(ret);
+
+          out_tag.resize(out_tag_len);
+
+          out = iovecs.Output();
+          EXPECT_EQ(Bytes(ct), Bytes(out.data(), out.size()));
+          EXPECT_EQ(Bytes(tag), Bytes(out_tag.data(), out_tag.size()));
+        }
+      } else {
+        out.resize(ct.size());
+        out_tag.resize(tag.size());
+        OPENSSL_memcpy(out.data(), ct.data(), ct.size());
+        OPENSSL_memcpy(out_tag.data(), tag.data(), tag.size());
+      }
+
+      if (detached) {
+        if (aead_config.flags & kVariableTag) {
+          // API not supported, thus nothing to test.
+          t->SkipCurrent();
+          return;
+        }
+
+        // Test the openv_detached API.
+        for (const auto &splits :
+             InterestingSplitsForLength(out.size(), /*block_size=*/16)) {
+          SCOPED_TRACE(FormatSplits(splits));
+          TestIOVecs iovecs = TestIOVecs::Split(out, splits, in_place);
+
+          // The "stateful" AEADs for implementing pre-AEAD cipher suites need
+          // to be reset after each operation.
+          ctx.Reset();
+          ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
+              ctx.get(), aead_config.func(), key.data(), key.size(), tag_len,
+              evp_aead_open));
+
+          int ret = EVP_AEAD_CTX_openv_detached(
+              ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(),
+              nonce.data(), nonce.size(), out_tag.data(), out_tag.size(),
+              advecs.ivecs().data(), advecs.ivecs().size());
+
+          if (t->HasAttribute("FAILS")) {
+            ASSERT_FALSE(ret) << "Decrypted bad data";
+            ERR_clear_error();
+            continue;
+          }
+
+          if (!ret && ERR_equals(ERR_peek_error(), ERR_LIB_CIPHER,
+                                 CIPHER_R_CTRL_NOT_IMPLEMENTED)) {
+            ERR_clear_error();
+            t->SkipCurrent();
+            return;
+          }
+
+          ASSERT_TRUE(ret) << "Failed to decrypt: "
+                           << ERR_reason_error_string(ERR_get_error());
+          std::vector<uint8_t> out2 = iovecs.Output();
+          EXPECT_EQ(Bytes(in), Bytes(out2));
+
+          // The "stateful" AEADs for implementing pre-AEAD cipher suites need
+          // to be reset after each operation.
+          ctx.Reset();
+          ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
+              ctx.get(), aead_config.func(), key.data(), key.size(), tag_len,
+              evp_aead_open));
+
+          // Garbage at the end isn't ignored.
+          out_tag.push_back(0);
+          ASSERT_EQ(out2.size(), out.size());
+          EXPECT_FALSE(EVP_AEAD_CTX_openv_detached(
+              ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(),
+              nonce.data(), nonce.size(), out_tag.data(), out_tag.size(),
+              advecs.ivecs().data(), advecs.ivecs().size()))
+              << "Decrypted bad data with trailing garbage.";
+          ERR_clear_error();
+
+          // The "stateful" AEADs for implementing pre-AEAD cipher suites need
+          // to be reset after each operation.
+          ctx.Reset();
+          ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
+              ctx.get(), aead_config.func(), key.data(), key.size(), tag_len,
+              evp_aead_open));
+
+          // Verify integrity is checked.
+          out_tag[0] ^= 0x80;
+          out_tag.resize(out_tag.size() - 1);
+          ASSERT_EQ(out2.size(), out.size());
+          EXPECT_FALSE(EVP_AEAD_CTX_openv_detached(
+              ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(),
+              nonce.data(), nonce.size(), out_tag.data(), out_tag.size(),
+              advecs.ivecs().data(), advecs.ivecs().size()))
+              << "Decrypted bad data with corrupted byte.";
+          ERR_clear_error();
+          out_tag[0] ^= 0x80;
+
+          ctx.Reset();
+          ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
+              ctx.get(), aead_config.func(), key.data(), key.size(), tag_len,
+              evp_aead_open));
+
+          // Check edge case for tag length.
+          EXPECT_FALSE(EVP_AEAD_CTX_openv_detached(
+              ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(),
+              nonce.data(), nonce.size(), out_tag.data(), 0,
+              advecs.ivecs().data(), advecs.ivecs().size()))
+              << "Decrypted bad data with corrupted byte.";
+          ERR_clear_error();
+        }
+      } else {
+        // Test the openv API. Make sure even the tag can be split.
+        std::vector<uint8_t> combined(out);
+        combined.insert(combined.end(), out_tag.begin(), out_tag.end());
+        for (const auto &splits :
+             InterestingSplitsForLength(combined.size(), /*block_size=*/16)) {
+          SCOPED_TRACE(FormatSplits(splits));
+          TestIOVecs iovecs = TestIOVecs::Split(combined, splits, in_place);
+
+          // The "stateful" AEADs for implementing pre-AEAD cipher suites need
+          // to be reset after each operation.
+          ctx.Reset();
+          ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
+              ctx.get(), aead_config.func(), key.data(), key.size(), tag_len,
+              evp_aead_open));
+
+          size_t plaintext_len;
+          int ret = EVP_AEAD_CTX_openv(
+              ctx.get(), iovecs.iovecs().data(), iovecs.iovecs().size(),
+              &plaintext_len, nonce.data(), nonce.size(), advecs.ivecs().data(),
+              advecs.ivecs().size());
+
+          if (t->HasAttribute("FAILS")) {
+            ASSERT_FALSE(ret) << "Decrypted bad data";
+            ERR_clear_error();
+            continue;
+          }
+
+          if (!ret && ERR_equals(ERR_peek_error(), ERR_LIB_CIPHER,
+                                 CIPHER_R_CTRL_NOT_IMPLEMENTED)) {
+            ERR_clear_error();
+            t->SkipCurrent();
+            return;
+          }
+
+          ASSERT_TRUE(ret) << "Failed to decrypt: "
+                           << ERR_reason_error_string(ERR_get_error());
+          std::vector<uint8_t> out2 = iovecs.Output();
+          out2.resize(plaintext_len);
+          EXPECT_EQ(Bytes(in), Bytes(out2));
+
+          // The "stateful" AEADs for implementing pre-AEAD cipher suites need
+          // to be reset after each operation.
+          ctx.Reset();
+          ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
+              ctx.get(), aead_config.func(), key.data(), key.size(), tag_len,
+              evp_aead_open));
+
+          // Garbage at the end isn't ignored.
+          std::vector<uint8_t> combined_wrecked(combined);
+          combined_wrecked.push_back(0);
+          TestIOVecs wrecked_iovecs =
+              TestIOVecs::Split(combined_wrecked, splits, in_place);
+          EXPECT_FALSE(EVP_AEAD_CTX_openv(
+              ctx.get(), wrecked_iovecs.iovecs().data(),
+              wrecked_iovecs.iovecs().size(), &plaintext_len, nonce.data(),
+              nonce.size(), advecs.ivecs().data(), advecs.ivecs().size()))
+              << "Decrypted bad data with trailing garbage.";
+          ERR_clear_error();
+
+          // The "stateful" AEADs for implementing pre-AEAD cipher suites need
+          // to be reset after each operation.
+          ctx.Reset();
+          ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
+              ctx.get(), aead_config.func(), key.data(), key.size(), tag_len,
+              evp_aead_open));
+
+          // Verify integrity is checked by changing the last byte.
+          combined_wrecked = combined;
+          combined_wrecked.back() ^= 0x80;
+          wrecked_iovecs =
+              TestIOVecs::Split(combined_wrecked, splits, in_place);
+          EXPECT_FALSE(EVP_AEAD_CTX_openv(
+              ctx.get(), wrecked_iovecs.iovecs().data(),
+              wrecked_iovecs.iovecs().size(), &plaintext_len, nonce.data(),
+              nonce.size(), advecs.ivecs().data(), advecs.ivecs().size()))
+              << "Decrypted bad data with corrupted byte.";
+          ERR_clear_error();
+
+          ctx.Reset();
+          ASSERT_TRUE(EVP_AEAD_CTX_init_with_direction(
+              ctx.get(), aead_config.func(), key.data(), key.size(), tag_len,
+              evp_aead_open));
+
+          // Check edge case for tag length.
+          combined_wrecked = combined;
+          combined_wrecked.pop_back();
+          std::vector<size_t> splits_wrecked = splits;
+          for (size_t &split : splits_wrecked) {
+            split = std::min(split, combined_wrecked.size());
+          }
+          wrecked_iovecs =
+              TestIOVecs::Split(combined_wrecked, splits_wrecked, in_place);
+          EXPECT_FALSE(EVP_AEAD_CTX_openv(
+              ctx.get(), wrecked_iovecs.iovecs().data(),
+              wrecked_iovecs.iovecs().size(), &plaintext_len, nonce.data(),
+              nonce.size(), advecs.ivecs().data(), advecs.ivecs().size()))
+              << "Decrypted bad data with corrupted byte.";
+          ERR_clear_error();
+        }
+      }
+    }
+  });
+}
+
+TEST_P(PerAEADTest, TestVectorIOVecOpenV) {
+  RunIOVecTests(GetParam(), /*in_place=*/false, /*detached=*/false);
+}
+
+TEST_P(PerAEADTest, TestVectorIOVecOpenVDetached) {
+  RunIOVecTests(GetParam(), /*in_place=*/false, /*detached=*/true);
+}
+
+TEST_P(PerAEADTest, TestVectorIOVecOpenVInPlace) {
+  RunIOVecTests(GetParam(), /*in_place=*/true, /*detached=*/false);
+}
+
+TEST_P(PerAEADTest, TestVectorIOVecOpenVDetachedInPlace) {
+  RunIOVecTests(GetParam(), /*in_place=*/true, /*detached=*/true);
+}
+
 TEST_P(PerAEADTest, CleanupAfterInitFailure) {
   uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
   OPENSSL_memset(key, 0, sizeof(key));
diff --git a/crypto/cipher/e_aesctrhmac.cc b/crypto/cipher/e_aesctrhmac.cc
index 0970a49..7530fef 100644
--- a/crypto/cipher/e_aesctrhmac.cc
+++ b/crypto/cipher/e_aesctrhmac.cc
@@ -249,6 +249,9 @@
     nullptr /* open */,
     aead_aes_ctr_hmac_sha256_seal_scatter,
     aead_aes_ctr_hmac_sha256_open_gather,
+    nullptr /* openv */,
+    nullptr /* sealv */,
+    nullptr /* openv_detached */,
     nullptr /* get_iv */,
     nullptr /* tag_len */,
 };
@@ -266,6 +269,9 @@
     nullptr /* open */,
     aead_aes_ctr_hmac_sha256_seal_scatter,
     aead_aes_ctr_hmac_sha256_open_gather,
+    nullptr /* openv */,
+    nullptr /* sealv */,
+    nullptr /* openv_detached */,
     nullptr /* get_iv */,
     nullptr /* tag_len */,
 };
diff --git a/crypto/cipher/e_aeseax.cc b/crypto/cipher/e_aeseax.cc
index 0a567ad..4f6a774 100644
--- a/crypto/cipher/e_aeseax.cc
+++ b/crypto/cipher/e_aeseax.cc
@@ -263,6 +263,9 @@
     nullptr,  // open
     aead_aes_eax_seal_scatter,
     aead_aes_eax_open_gather,
+    nullptr,  // openv
+    nullptr,  // sealv
+    nullptr,  // openv_detached
     nullptr,  // get_iv
     nullptr,  // tag_len
 };
@@ -280,6 +283,9 @@
     nullptr,  // open
     aead_aes_eax_seal_scatter,
     aead_aes_eax_open_gather,
+    nullptr,  // openv
+    nullptr,  // sealv
+    nullptr,  // openv_detached
     nullptr,  // get_iv
     nullptr,  // tag_len
 };
diff --git a/crypto/cipher/e_aesgcmsiv.cc b/crypto/cipher/e_aesgcmsiv.cc
index 37fb18c..c0ac927 100644
--- a/crypto/cipher/e_aesgcmsiv.cc
+++ b/crypto/cipher/e_aesgcmsiv.cc
@@ -509,6 +509,9 @@
     nullptr /* open */,
     aead_aes_gcm_siv_asm_seal_scatter,
     aead_aes_gcm_siv_asm_open_gather,
+    nullptr /* openv */,
+    nullptr /* sealv */,
+    nullptr /* openv_detached */,
     nullptr /* get_iv */,
     nullptr /* tag_len */,
 };
@@ -526,6 +529,9 @@
     nullptr /* open */,
     aead_aes_gcm_siv_asm_seal_scatter,
     aead_aes_gcm_siv_asm_open_gather,
+    nullptr /* openv */,
+    nullptr /* sealv */,
+    nullptr /* openv_detached */,
     nullptr /* get_iv */,
     nullptr /* tag_len */,
 };
@@ -875,6 +881,9 @@
     nullptr /* open */,
     aead_aes_gcm_siv_seal_scatter,
     aead_aes_gcm_siv_open_gather,
+    nullptr /* openv */,
+    nullptr /* sealv */,
+    nullptr /* openv_detached */,
     nullptr /* get_iv */,
     nullptr /* tag_len */,
 };
@@ -892,6 +901,9 @@
     nullptr /* open */,
     aead_aes_gcm_siv_seal_scatter,
     aead_aes_gcm_siv_open_gather,
+    nullptr /* openv */,
+    nullptr /* sealv */,
+    nullptr /* openv_detached */,
     nullptr /* get_iv */,
     nullptr /* tag_len */,
 };
diff --git a/crypto/cipher/e_chacha20poly1305.cc b/crypto/cipher/e_chacha20poly1305.cc
index f13999c..97c6b79 100644
--- a/crypto/cipher/e_chacha20poly1305.cc
+++ b/crypto/cipher/e_chacha20poly1305.cc
@@ -311,6 +311,9 @@
     nullptr /* open */,
     aead_chacha20_poly1305_seal_scatter,
     aead_chacha20_poly1305_open_gather,
+    nullptr,  // openv
+    nullptr,  // sealv
+    nullptr,  // openv_detached
     nullptr,  // get_iv
     nullptr,  // tag_len
 };
@@ -328,6 +331,9 @@
     nullptr /* open */,
     aead_xchacha20_poly1305_seal_scatter,
     aead_xchacha20_poly1305_open_gather,
+    nullptr,  // openv
+    nullptr,  // sealv
+    nullptr,  // openv_detached
     nullptr,  // get_iv
     nullptr,  // tag_len
 };
diff --git a/crypto/cipher/e_tls.cc b/crypto/cipher/e_tls.cc
index b0f3ab3..7be0644 100644
--- a/crypto/cipher/e_tls.cc
+++ b/crypto/cipher/e_tls.cc
@@ -427,6 +427,9 @@
     aead_tls_open,
     aead_tls_seal_scatter,
     nullptr,  // open_gather
+    nullptr,  // openv
+    nullptr,  // sealv
+    nullptr,  // openv_detached
     nullptr,  // get_iv
     aead_tls_tag_len,
 };
@@ -444,6 +447,9 @@
     aead_tls_open,
     aead_tls_seal_scatter,
     nullptr,          // open_gather
+    nullptr,          // openv
+    nullptr,          // sealv
+    nullptr,          // openv_detached
     aead_tls_get_iv,  // get_iv
     aead_tls_tag_len,
 };
@@ -461,6 +467,9 @@
     aead_tls_open,
     aead_tls_seal_scatter,
     nullptr,  // open_gather
+    nullptr,  // openv
+    nullptr,  // sealv
+    nullptr,  // openv_detached
     nullptr,  // get_iv
     aead_tls_tag_len,
 };
@@ -478,6 +487,9 @@
     aead_tls_open,
     aead_tls_seal_scatter,
     nullptr,  // open_gather
+    nullptr,  // openv
+    nullptr,  // sealv
+    nullptr,  // openv_detached
     nullptr,  // get_iv
     aead_tls_tag_len,
 };
@@ -495,6 +507,9 @@
     aead_tls_open,
     aead_tls_seal_scatter,
     nullptr,          // open_gather
+    nullptr,          // openv
+    nullptr,          // sealv
+    nullptr,          // openv_detached
     aead_tls_get_iv,  // get_iv
     aead_tls_tag_len,
 };
@@ -512,6 +527,9 @@
     aead_tls_open,
     aead_tls_seal_scatter,
     nullptr,  // open_gather
+    nullptr,  // openv
+    nullptr,  // sealv
+    nullptr,  // openv_detached
     nullptr,  // get_iv
     aead_tls_tag_len,
 };
@@ -529,6 +547,9 @@
     aead_tls_open,
     aead_tls_seal_scatter,
     nullptr,          // open_gather
+    nullptr,          // openv
+    nullptr,          // sealv
+    nullptr,          // openv_detached
     aead_tls_get_iv,  // get_iv
     aead_tls_tag_len,
 };
diff --git a/crypto/fipsmodule/cipher/aead.cc.inc b/crypto/fipsmodule/cipher/aead.cc.inc
index b9e4af1..1e477c3 100644
--- a/crypto/fipsmodule/cipher/aead.cc.inc
+++ b/crypto/fipsmodule/cipher/aead.cc.inc
@@ -14,12 +14,15 @@
 
 #include <openssl/aead.h>
 
+#include <optional>
+
 #include <assert.h>
 #include <string.h>
 
 #include <openssl/cipher.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
+#include <openssl/span.h>
 
 #include "../../internal.h"
 #include "internal.h"
@@ -123,19 +126,48 @@
                       size_t max_out_len, const uint8_t *nonce,
                       size_t nonce_len, const uint8_t *in, size_t in_len,
                       const uint8_t *ad, size_t ad_len) {
-  if (in_len + ctx->aead->overhead < in_len /* overflow */) {
-    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
-    goto error;
-  }
+  bool ok = false;
+  bssl::Cleanup cleanup([&] {
+    if (!ok) {
+      // In the event of an error, clear the output buffer so that a caller
+      // that doesn't check the return value doesn't send raw data.
+      OPENSSL_memset(out, 0, max_out_len);
+      *out_len = 0;
+    }
+  });
 
   if (max_out_len < in_len) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
-    goto error;
+    return 0;
+  }
+
+  if (!ctx->aead->seal_scatter) {
+    CRYPTO_IOVEC iovec[1];
+    iovec[0].in = in;
+    iovec[0].out = out;
+    iovec[0].len = in_len;
+    CRYPTO_IVEC aadvec[1];
+    aadvec[0].in = ad;
+    aadvec[0].len = ad_len;
+    if (!EVP_AEAD_CTX_sealv(ctx, iovec, 1, out + in_len, out_len,
+                            max_out_len - in_len, nonce, nonce_len, aadvec,
+                            1)) {
+      *out_len = 0;
+      return 0;
+    }
+    *out_len += in_len;
+    ok = true;
+    return 1;
+  }
+
+  if (in_len + ctx->aead->overhead < in_len /* overflow */) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
+    return 0;
   }
 
   if (!check_alias(in, in_len, out, max_out_len)) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT);
-    goto error;
+    return 0;
   }
 
   size_t out_tag_len;
@@ -143,14 +175,10 @@
                               max_out_len - in_len, nonce, nonce_len, in,
                               in_len, nullptr, 0, ad, ad_len)) {
     *out_len = in_len + out_tag_len;
+    ok = true;
     return 1;
   }
 
-error:
-  // In the event of an error, clear the output buffer so that a caller
-  // that doesn't check the return value doesn't send raw data.
-  OPENSSL_memset(out, 0, max_out_len);
-  *out_len = 0;
   return 0;
 }
 
@@ -161,31 +189,212 @@
                               size_t in_len, const uint8_t *extra_in,
                               size_t extra_in_len, const uint8_t *ad,
                               size_t ad_len) {
+  bool ok = false;
+  bssl::Cleanup cleanup([&] {
+    if (!ok) {
+      // In the event of an error, clear the output buffer so that a caller
+      // that doesn't check the return value doesn't send raw data.
+      OPENSSL_memset(out, 0, in_len);
+      OPENSSL_memset(out_tag, 0, max_out_tag_len);
+      *out_tag_len = 0;
+    }
+  });
+
+  if (!ctx->aead->seal_scatter) {
+    CRYPTO_IOVEC iovec[2];
+    iovec[0].in = in;
+    iovec[0].out = out;
+    iovec[0].len = in_len;
+    iovec[1].in = extra_in;
+    iovec[1].out = out_tag;
+    iovec[1].len = extra_in_len;
+    CRYPTO_IVEC aadvec[1];
+    aadvec[0].in = ad;
+    aadvec[0].len = ad_len;
+    if (!EVP_AEAD_CTX_sealv(ctx, iovec, extra_in_len ? 2 : 1,
+                            out_tag + extra_in_len, out_tag_len,
+                            max_out_tag_len - extra_in_len, nonce, nonce_len,
+                            aadvec, 1)) {
+      *out_tag_len = 0;
+      return 0;
+    }
+    *out_tag_len += extra_in_len;
+    ok = true;
+    return 1;
+  }
+
   // |in| and |out| may alias exactly, |out_tag| may not alias.
   if (!check_alias(in, in_len, out, in_len) ||
       buffers_alias(out, in_len, out_tag, max_out_tag_len) ||
       buffers_alias(in, in_len, out_tag, max_out_tag_len)) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT);
-    goto error;
+    return 0;
   }
 
   if (!ctx->aead->seal_scatter_supports_extra_in && extra_in_len) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_INVALID_OPERATION);
-    goto error;
+    return 0;
   }
 
   if (ctx->aead->seal_scatter(ctx, out, out_tag, out_tag_len, max_out_tag_len,
                               nonce, nonce_len, in, in_len, extra_in,
                               extra_in_len, ad, ad_len)) {
+    ok = true;
     return 1;
   }
 
-error:
-  // In the event of an error, clear the output buffer so that a caller
-  // that doesn't check the return value doesn't send raw data.
-  OPENSSL_memset(out, 0, in_len);
-  OPENSSL_memset(out_tag, 0, max_out_tag_len);
-  *out_tag_len = 0;
+  return 0;
+}
+
+static bool check_iovec_internal_alias(bssl::Span<const CRYPTO_IOVEC> iovecs) {
+  for (size_t i = 0; i < iovecs.size(); ++i) {
+    // Same index check.
+    if (!check_alias(iovecs[i].in, iovecs[i].len, iovecs[i].out,
+                     iovecs[i].len)) {
+      return false;
+    }
+#if !defined(NDEBUG)
+    // Unrealistic cases; they'd be harmful but also extremely unlikely anyone
+    // will ever get those wrong. Thus skip them in release builds.
+    for (size_t j = i + 1; j < iovecs.size(); ++j) {
+      if (buffers_alias(iovecs[i].in, iovecs[i].len,  //
+                        iovecs[j].out, iovecs[j].len) ||
+          buffers_alias(iovecs[i].out, iovecs[i].len,  //
+                        iovecs[j].in, iovecs[j].len) ||
+          buffers_alias(iovecs[i].out, iovecs[i].len,  //
+                        iovecs[j].out, iovecs[j].len)) {
+        return false;
+      }
+    }
+#endif
+  }
+  return true;
+}
+
+#if !defined(NDEBUG)
+static bool check_ivec_buf_alias(bssl::Span<const CRYPTO_IVEC> ivecs,
+                                 const uint8_t *buf, size_t buf_len) {
+  for (const CRYPTO_IVEC &ivec : ivecs) {
+    if (buffers_alias(ivec.in, ivec.len, buf, buf_len)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+static bool check_iovec_out_ivec_alias(bssl::Span<const CRYPTO_IOVEC> iovecs,
+                                       bssl::Span<const CRYPTO_IVEC> ivecs) {
+  for (const CRYPTO_IOVEC &iovec : iovecs) {
+    if (!check_ivec_buf_alias(ivecs, iovec.out, iovec.len)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+static bool check_iovec_buf_alias(bssl::Span<const CRYPTO_IOVEC> iovecs,
+                                  const uint8_t *buf, size_t buf_len) {
+  for (const CRYPTO_IOVEC &iovec : iovecs) {
+    if (buffers_alias(iovec.in, iovec.len, buf, buf_len)) {
+      return false;
+    }
+    if (buffers_alias(iovec.out, iovec.len, buf, buf_len)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+static bool check_iovec_out_buf_alias(bssl::Span<const CRYPTO_IOVEC> iovecs,
+                                      const uint8_t *buf, size_t buf_len) {
+  for (const CRYPTO_IOVEC &iovec : iovecs) {
+    if (buffers_alias(iovec.out, iovec.len, buf, buf_len)) {
+      return false;
+    }
+  }
+  return true;
+}
+#endif
+
+static bool check_iovec_alias(bssl::Span<const CRYPTO_IOVEC> iovecs,
+                              bssl::Span<const CRYPTO_IVEC> aadvecs,
+                              const uint8_t *out, size_t out_len,
+                              const uint8_t *in1, size_t in1_len,
+                              const uint8_t *in2, size_t in2_len) {
+  return
+#if !defined(NDEBUG)
+      // Unrealistic cases; they'd be harmful but also extremely unlikely anyone
+      // will ever get those wrong. Thus skip them in release builds.
+      //
+      // iovec.out <-> aadvec.
+      check_iovec_out_ivec_alias(iovecs, aadvecs) &&
+      // iovec <-> out.
+      check_iovec_buf_alias(iovecs, out, out_len) &&
+      // iovec.out <-> in1.
+      check_iovec_out_buf_alias(iovecs, in1, in1_len) &&
+      // iovec.out <-> in2.
+      check_iovec_out_buf_alias(iovecs, in2, in2_len) &&
+      // aadvec <-> out.
+      check_ivec_buf_alias(aadvecs, out, out_len) &&
+      // out <-> in1.
+      !buffers_alias(out, out_len, in1, in1_len) &&
+      // out <-> in2.
+      !buffers_alias(out, out_len, in2, in2_len) &&
+#endif
+      // iovec <-> iovec.
+      check_iovec_internal_alias(iovecs);
+}
+
+static void clear_iovec(bssl::Span<const CRYPTO_IOVEC> iovecs) {
+  for (const CRYPTO_IOVEC &iovec : iovecs) {
+    OPENSSL_memset(iovec.out, 0, iovec.len);
+  }
+}
+
+int EVP_AEAD_CTX_sealv(const EVP_AEAD_CTX *ctx, const CRYPTO_IOVEC *iovec,
+                       size_t num_iovec, uint8_t *out_tag, size_t *out_tag_len,
+                       size_t max_out_tag_len, const uint8_t *nonce,
+                       size_t nonce_len, const CRYPTO_IVEC *aadvec,
+                       size_t num_aadvec) {
+  bssl::Span<const CRYPTO_IOVEC> iovecs(iovec, num_iovec);
+  bssl::Span<const CRYPTO_IVEC> aadvecs(aadvec, num_aadvec);
+
+  bool ok = false;
+  bssl::Cleanup cleanup([&] {
+    if (!ok) {
+      // In the event of an error, clear the output buffer so that a caller
+      // that doesn't check the return value doesn't send raw data.
+      clear_iovec(iovecs);
+      OPENSSL_memset(out_tag, 0, max_out_tag_len);
+      *out_tag_len = 0;
+    }
+  });
+
+  if (!bssl::iovec::IsValid(iovecs) || !bssl::iovec::IsValid(aadvecs)) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
+    return 0;
+  }
+
+  // Enforce aliasing rules: no output may alias any input, with the one
+  // exception that an iovec member's |in| and |out| pointers may be identical
+  // for in-place operation.
+  if (!check_iovec_alias(iovecs, aadvecs, out_tag, max_out_tag_len, nonce,
+                         nonce_len, nullptr, 0)) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT);
+    return 0;
+  }
+
+  if (!ctx->aead->sealv) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_CTRL_NOT_IMPLEMENTED);
+    return 0;
+  }
+
+  if (ctx->aead->sealv(ctx, iovecs, out_tag, out_tag_len, max_out_tag_len,
+                       nonce, nonce_len, aadvecs)) {
+    ok = true;
+    return 1;
+  }
+
   return 0;
 }
 
@@ -193,17 +402,81 @@
                       size_t max_out_len, const uint8_t *nonce,
                       size_t nonce_len, const uint8_t *in, size_t in_len,
                       const uint8_t *ad, size_t ad_len) {
-  size_t plaintext_len;
+  bool ok = false;
+  bssl::Cleanup cleanup([&] {
+    if (!ok) {
+      // In the event of an error, clear the output buffer so that a caller
+      // that doesn't check the return value doesn't try and process bad
+      // data.
+      OPENSSL_memset(out, 0, max_out_len);
+      *out_len = 0;
+    }
+  });
+
+  if (!ctx->aead->open && !ctx->aead->open_gather) {
+    if (ctx->tag_len) {
+      // If the tag length is known, the caller only needs to provide enough
+      // space for in_len - tag_len.
+      if (in_len < ctx->tag_len) {
+        OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT);
+        return 0;
+      }
+      size_t plaintext_len = in_len - ctx->tag_len;
+      if (max_out_len < plaintext_len) {
+        OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
+        return 0;
+      }
+
+      CRYPTO_IOVEC iovec[1];
+      iovec[0].in = in;
+      iovec[0].out = out;
+      iovec[0].len = plaintext_len;
+      CRYPTO_IVEC aadvec[1];
+      aadvec[0].in = ad;
+      aadvec[0].len = ad_len;
+      if (!EVP_AEAD_CTX_openv_detached(ctx, iovec, 1, nonce, nonce_len,
+                                       in + plaintext_len, ctx->tag_len, aadvec,
+                                       1)) {
+        return 0;
+      }
+      *out_len = plaintext_len;
+      ok = true;
+      return 1;
+    } else {
+      if (max_out_len < in_len) {
+        // Variable tag length AEADs need to be able to decrypt the entire
+        // plaintext before they can split it up. So the caller has to provide
+        // sufficient max_out_len for temporary data.
+        OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
+        return 0;
+      }
+      CRYPTO_IOVEC iovec[1];
+      iovec[0].in = in;
+      iovec[0].out = out;
+      iovec[0].len = in_len;
+      CRYPTO_IVEC aadvec[1];
+      aadvec[0].in = ad;
+      aadvec[0].len = ad_len;
+      if (!EVP_AEAD_CTX_openv(ctx, iovec, 1, out_len, nonce, nonce_len, aadvec,
+                              1)) {
+        return 0;
+      }
+      ok = true;
+      return 1;
+    }
+  }
+
   if (!check_alias(in, in_len, out, max_out_len)) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT);
-    goto error;
+    return 0;
   }
 
   if (ctx->aead->open) {
     if (!ctx->aead->open(ctx, out, out_len, max_out_len, nonce, nonce_len, in,
                          in_len, ad, ad_len)) {
-      goto error;
+      return 0;
     }
+    ok = true;
     return 1;
   }
 
@@ -213,26 +486,22 @@
 
   if (in_len < ctx->tag_len) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT);
-    goto error;
+    return 0;
   }
 
-  plaintext_len = in_len - ctx->tag_len;
+  size_t plaintext_len = in_len - ctx->tag_len;
   if (max_out_len < plaintext_len) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
-    goto error;
+    return 0;
   }
-  if (EVP_AEAD_CTX_open_gather(ctx, out, nonce, nonce_len, in, plaintext_len,
-                               in + plaintext_len, ctx->tag_len, ad, ad_len)) {
+
+  if (ctx->aead->open_gather(ctx, out, nonce, nonce_len, in, plaintext_len,
+                             in + plaintext_len, ctx->tag_len, ad, ad_len)) {
     *out_len = plaintext_len;
+    ok = true;
     return 1;
   }
 
-error:
-  // In the event of an error, clear the output buffer so that a caller
-  // that doesn't check the return value doesn't try and process bad
-  // data.
-  OPENSSL_memset(out, 0, max_out_len);
-  *out_len = 0;
   return 0;
 }
 
@@ -241,26 +510,175 @@
                              const uint8_t *in, size_t in_len,
                              const uint8_t *in_tag, size_t in_tag_len,
                              const uint8_t *ad, size_t ad_len) {
+  bool ok = false;
+  bssl::Cleanup cleanup([&] {
+    if (!ok) {
+      // In the event of an error, clear the output buffer so that a caller
+      // that doesn't check the return value doesn't try and process bad
+      // data.
+      OPENSSL_memset(out, 0, in_len);
+    }
+  });
+
+  if (!ctx->aead->open_gather) {
+    CRYPTO_IOVEC iovec[1];
+    iovec[0].in = in;
+    iovec[0].out = out;
+    iovec[0].len = in_len;
+    CRYPTO_IVEC aadvec[1];
+    aadvec[0].in = ad;
+    aadvec[0].len = ad_len;
+    if (EVP_AEAD_CTX_openv_detached(ctx, iovec, 1, nonce, nonce_len, in_tag,
+                                    in_tag_len, aadvec, 1)) {
+      ok = true;
+      return 1;
+    }
+    return 0;
+  }
+
   if (!check_alias(in, in_len, out, in_len)) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT);
-    goto error;
+    return 0;
   }
 
   if (!ctx->aead->open_gather) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_CTRL_NOT_IMPLEMENTED);
-    goto error;
+    return 0;
   }
 
   if (ctx->aead->open_gather(ctx, out, nonce, nonce_len, in, in_len, in_tag,
                              in_tag_len, ad, ad_len)) {
+    ok = true;
     return 1;
   }
 
-error:
-  // In the event of an error, clear the output buffer so that a caller
-  // that doesn't check the return value doesn't try and process bad
-  // data.
-  OPENSSL_memset(out, 0, in_len);
+  return 0;
+}
+
+int EVP_AEAD_CTX_openv(const EVP_AEAD_CTX *ctx, const CRYPTO_IOVEC *iovec,
+                       size_t num_iovec, size_t *out_total_bytes,
+                       const uint8_t *nonce, size_t nonce_len,
+                       const CRYPTO_IVEC *aadvec, size_t num_aadvec) {
+  bssl::Span<const CRYPTO_IOVEC> iovecs(iovec, num_iovec);
+  bssl::Span<const CRYPTO_IVEC> aadvecs(aadvec, num_aadvec);
+
+  bool ok = false;
+  bssl::Cleanup cleanup([&] {
+    if (!ok) {
+      // In the event of an error, clear the output buffer so that a caller
+      // that doesn't check the return value doesn't try and process bad
+      // data.
+      clear_iovec(iovecs);
+      *out_total_bytes = 0;
+    }
+  });
+
+  if (!bssl::iovec::IsValid(iovecs) || !bssl::iovec::IsValid(aadvecs)) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
+    return 0;
+  }
+
+  // Enforce aliasing rules: no output may alias any input, with the one
+  // exception that an iovec member's |in| and |out| pointers may be identical
+  // for in-place operation.
+  if (!check_iovec_alias(iovecs, aadvecs, nullptr, 0, nonce, nonce_len, nullptr,
+                         0)) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT);
+    return 0;
+  }
+
+  if (!ctx->aead->openv) {
+    if (ctx->tag_len && ctx->aead->openv_detached) {
+      // Try with a detached tag.
+      bssl::InplaceVector<CRYPTO_IOVEC, CRYPTO_IOVEC_MAX> detached_iovecs;
+      detached_iovecs.CopyFrom(iovecs);
+
+      uint8_t tagbuf[EVP_AEAD_MAX_OVERHEAD];
+      std::optional<bssl::Span<const uint8_t>> tag =
+          bssl::iovec::GetAndRemoveSuffix(
+              bssl::Span(tagbuf).first(ctx->tag_len),
+              bssl::Span(detached_iovecs));
+
+      if (!tag.has_value()) {  // I.e. no |ctx->tag_len| bytes available.
+        OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT);
+        return 0;
+      }
+
+      if (ctx->aead->openv_detached(ctx, detached_iovecs, nonce, nonce_len,
+                                    tag->data(), tag->size(), aadvecs)) {
+        ok = true;
+        *out_total_bytes =
+            bssl::iovec::TotalLength(bssl::Span(detached_iovecs));
+        return 1;
+      }
+      return 0;
+    }
+
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_CTRL_NOT_IMPLEMENTED);
+    return 0;
+  }
+
+  if (ctx->aead->openv(ctx, iovecs, out_total_bytes, nonce, nonce_len,
+                       aadvecs)) {
+    ok = true;
+    return 1;
+  }
+
+  return 0;
+}
+
+int EVP_AEAD_CTX_openv_detached(const EVP_AEAD_CTX *ctx,
+                                const CRYPTO_IOVEC *iovec, size_t num_iovec,
+                                const uint8_t *nonce, size_t nonce_len,
+                                const uint8_t *in_tag, size_t in_tag_len,
+                                const CRYPTO_IVEC *aadvec, size_t num_aadvec) {
+  bssl::Span<const CRYPTO_IOVEC> iovecs(iovec, num_iovec);
+  bssl::Span<const CRYPTO_IVEC> aadvecs(aadvec, num_aadvec);
+
+  bool ok = false;
+  bssl::Cleanup cleanup([&] {
+    if (!ok) {
+      // In the event of an error, clear the output buffer so that a caller
+      // that doesn't check the return value doesn't try and process bad
+      // data.
+      clear_iovec(iovecs);
+    }
+  });
+
+  if (!bssl::iovec::IsValid(iovecs) || !bssl::iovec::IsValid(aadvecs)) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
+    return 0;
+  }
+  if (in_tag_len > EVP_AEAD_MAX_OPEN_OVERHEAD) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_TAG_SIZE);
+    return 0;
+  }
+
+  // Enforce aliasing rules: no output may alias any input, with the one
+  // exception that an iovec member's |in| and |out| pointers may be identical
+  // for in-place operation.
+  if (!check_iovec_alias(iovecs, aadvecs, nullptr, 0, nonce, nonce_len, in_tag,
+                         in_tag_len)) {
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_OUTPUT_ALIASES_INPUT);
+    return 0;
+  }
+
+  if (!ctx->aead->openv_detached) {
+    // AEADs with variable overhead may provide openv instead of openv_detached.
+    // While one might call openv and then, on success, discard the result if
+    // the length was wrong, this requires callers to predict the plaintext
+    // length first. We do not expect callers to do this, especially in the TLS
+    // CBC construction, where this length is sensitive to the Lucky 13 attack.
+    OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_CTRL_NOT_IMPLEMENTED);
+    return 0;
+  }
+
+  if (ctx->aead->openv_detached(ctx, iovecs, nonce, nonce_len, in_tag,
+                                in_tag_len, aadvecs)) {
+    ok = true;
+    return 1;
+  }
+
   return 0;
 }
 
diff --git a/crypto/fipsmodule/cipher/cipher.cc.inc b/crypto/fipsmodule/cipher/cipher.cc.inc
index f6c534d..53ff1e9 100644
--- a/crypto/fipsmodule/cipher/cipher.cc.inc
+++ b/crypto/fipsmodule/cipher/cipher.cc.inc
@@ -204,16 +204,6 @@
   return EVP_CipherInit_ex(ctx, cipher, impl, key, iv, 0);
 }
 
-// CopyToPrefix copies a span of bytes from |from| into |to|. It aborts if there
-// is not enough space.
-//
-// TODO(crbug.com/404286922): Can we simplify this in a C++20 world (e.g.
-// std::ranges::copy)? Must preserve range checking on the destination span.
-static void CopyToPrefix(bssl::Span<const uint8_t> from,
-                         bssl::Span<uint8_t> to) {
-  OPENSSL_memcpy(to.first(from.size()).data(), from.data(), from.size());
-}
-
 // block_remainder returns the number of bytes to remove from |len| to get a
 // multiple of |ctx|'s block size.
 static size_t block_remainder(const EVP_CIPHER_CTX *ctx, size_t len) {
@@ -286,12 +276,13 @@
   bssl::Span<uint8_t> out_span(out, max_out_len);
   if (buf_len != 0) {
     if (block_size - buf_len > in_span.size()) {
-      CopyToPrefix(in_span, bssl::Span(ctx->buf).subspan(buf_len));
+      bssl::CopyToPrefix(in_span, bssl::Span(ctx->buf).subspan(buf_len));
       ctx->buf_len += in_span.size();
       return 1;
     } else {
       size_t j = block_size - buf_len;
-      CopyToPrefix(in_span.first(j), bssl::Span(ctx->buf).subspan(buf_len));
+      bssl::CopyToPrefix(in_span.first(j),
+                         bssl::Span(ctx->buf).subspan(buf_len));
       if (out_span.size() < block_size) {
         OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
         return 0;
@@ -320,7 +311,7 @@
   }
 
   assert(in_span.size() < block_size);
-  CopyToPrefix(in_span, ctx->buf);
+  bssl::CopyToPrefix(in_span, ctx->buf);
   ctx->buf_len = in_span.size();
 
   *out_len = max_out_len - out_span.size();
@@ -453,7 +444,7 @@
       OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
       return 0;
     }
-    CopyToPrefix(bssl::Span(ctx->final).first(block_size), out_span);
+    bssl::CopyToPrefix(bssl::Span(ctx->final).first(block_size), out_span);
     ctx->final_used = 0;
     out_span = out_span.subspan(block_size);
   }
@@ -572,7 +563,7 @@
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
     return 0;
   }
-  CopyToPrefix(bssl::Span(ctx->final).first(payload), out_span);
+  bssl::CopyToPrefix(bssl::Span(ctx->final).first(payload), out_span);
   out_span = out_span.subspan(payload);
 
   *out_len = max_out_len - out_span.size();
diff --git a/crypto/fipsmodule/cipher/internal.h b/crypto/fipsmodule/cipher/internal.h
index 794db01..2446110 100644
--- a/crypto/fipsmodule/cipher/internal.h
+++ b/crypto/fipsmodule/cipher/internal.h
@@ -19,13 +19,16 @@
 
 #include <openssl/aead.h>
 #include <openssl/aes.h>
+#include <openssl/span.h>
 
 #include "../../internal.h"
 #include "../aes/internal.h"
 
-#if defined(__cplusplus)
+#include <algorithm>
+#include <functional>
+#include <optional>
+
 extern "C" {
-#endif
 
 
 // EVP_CIPH_MODE_MASK contains the bits of |flags| that represent the mode.
@@ -47,6 +50,13 @@
                              size_t tag_len, enum evp_aead_direction_t dir);
   void (*cleanup)(EVP_AEAD_CTX *);
 
+  // AEADs need to provide one of the following sets of methods:
+  //
+  // - openv + sealv: variable tag lenght AEAD.
+  // - openv_detached + sealv: fixed tag length AEAD.
+  // - open + seal_scatter: legacy variable tag length AEAD.
+  // - open_gather + seal_scatter: legacy fixed tag length AEAD.
+
   int (*open)(const EVP_AEAD_CTX *ctx, uint8_t *out, size_t *out_len,
               size_t max_out_len, const uint8_t *nonce, size_t nonce_len,
               const uint8_t *in, size_t in_len, const uint8_t *ad,
@@ -63,10 +73,25 @@
                      size_t in_len, const uint8_t *in_tag, size_t in_tag_len,
                      const uint8_t *ad, size_t ad_len);
 
+  int (*openv)(const EVP_AEAD_CTX *ctx, bssl::Span<const CRYPTO_IOVEC> iovecs,
+               size_t *out_total_bytes, const uint8_t *nonce, size_t nonce_len,
+               bssl::Span<const CRYPTO_IVEC> aadvecs);
+
+  int (*sealv)(const EVP_AEAD_CTX *ctx, bssl::Span<const CRYPTO_IOVEC> iovecs,
+               uint8_t *out_tag, size_t *out_tag_len, size_t max_out_tag_len,
+               const uint8_t *nonce, size_t nonce_len,
+               bssl::Span<const CRYPTO_IVEC> aadvecs);
+
+  int (*openv_detached)(const EVP_AEAD_CTX *ctx,
+                        bssl::Span<const CRYPTO_IOVEC> iovecs,
+                        const uint8_t *nonce, size_t nonce_len,
+                        const uint8_t *in_tag, size_t in_tag_len,
+                        bssl::Span<const CRYPTO_IVEC> aadvecs);
+
   int (*get_iv)(const EVP_AEAD_CTX *ctx, const uint8_t **out_iv,
                 size_t *out_len);
 
-  size_t (*tag_len)(const EVP_AEAD_CTX *ctx, size_t in_Len,
+  size_t (*tag_len)(const EVP_AEAD_CTX *ctx, size_t in_len,
                     size_t extra_in_len);
 };
 
@@ -130,8 +155,117 @@
   int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr);
 };
 
-#if defined(__cplusplus)
 }  // extern C
-#endif
+
+BSSL_NAMESPACE_BEGIN
+
+// CopySpan copies an entire span of bytes from |from| to |to|.
+//
+// The spans need to have the same length.
+inline void CopySpan(Span<const uint8_t> from, Span<uint8_t> to) {
+  BSSL_CHECK(from.size() == to.size());
+  std::copy(from.begin(), from.end(), to.begin());
+}
+
+// CopyToPrefix copies a span of bytes from |from| into |to|. It aborts
+// if there is not enough space.
+//
+// TODO(crbug.com/404286922): Can we simplify this in a C++20 world (e.g.
+// std::ranges::copy)? Must preserve range checking on the destination span.
+inline void CopyToPrefix(Span<const uint8_t> from, Span<uint8_t> to) {
+  CopySpan(from, to.first(from.size()));
+}
+
+// Generic CRYPTO_IOVEC/CRYPTO_IVEC helpers.
+namespace iovec {
+
+// IsValid returns whether the given |CRYPTO_IVEC| or |CRYPTO_IOVEC| is
+// valid for use with public APIs, i.e. does not contain more than |SIZE_MAX|
+// bytes and not more than |CRYPTO_IOVEC_MAX| chunks. Note that the `EVP_AEAD`
+// methods need to accept an arbitrary number of chunks.
+template <typename IVec>
+inline bool IsValid(Span<IVec> ivecs) {
+  if (ivecs.size() > CRYPTO_IOVEC_MAX) {
+    return false;
+  }
+  size_t allowed = SIZE_MAX;
+  for (const IVec &ivec : ivecs) {
+    size_t len = ivec.len;
+    if (len > allowed) {
+      return false;
+    }
+    allowed -= len;
+  }
+  return true;
+}
+
+// Length returns the total length in bytes of a given |CRYPTO_IVEC| or
+// |CRYPTO_IOVEC|.
+template <typename IVec>
+inline size_t TotalLength(Span<IVec> ivecs) {
+  size_t total = 0;
+  for (const IVec &ivec : ivecs) {
+    total += ivec.len;
+  }
+  return total;
+}
+
+// GetAndRemoveSuffix takes |suffix_buf.size()| final bytes from the given
+// |CRYPTO_IVEC| or |CRYPTO_IOVEC| (mutating said iovec to no longer contain
+// those bytes) and returns them.
+//
+// If the byte range is contained in a single chunk of |ivecs|, it will just
+// return that span pointing into |ivecs|; otherwise, it will copy the bytes
+// into |out| and return that.
+//
+// If |ivecs| is too short, returns |nullopt|.
+template <typename IVec, typename ReadFromT = const uint8_t *,
+          ReadFromT IVec::*ReadFrom = &IVec::in>
+inline std::optional<Span<const uint8_t>> GetAndRemoveSuffix(
+    Span<uint8_t> suffix_buf, Span<IVec> ivecs) {
+  // Get the trivial case out.
+  if (suffix_buf.empty()) {
+    return suffix_buf;
+  }
+  // Strip trailing zero length chunks.
+  while (!ivecs.empty() && ivecs.back().len == 0) {
+    ivecs = ivecs.first(ivecs.size() - 1);
+  }
+  if (ivecs.empty()) {
+    return std::nullopt;
+  }
+  // Is the requested chunk entirely contained? If so, just return it.
+  if (ivecs.back().len >= suffix_buf.size()) {
+    ivecs.back().len -= suffix_buf.size();
+    return Span(ivecs.back().*ReadFrom + ivecs.back().len, suffix_buf.size());
+  }
+  // Otherwise, collect it into the buffer while trimming |ivecs|.
+  Span<uint8_t> remaining = suffix_buf;
+  while (!ivecs.empty()) {
+    Span<const uint8_t> src(ivecs.back().*ReadFrom, ivecs.back().len);
+    if (src.size() >= remaining.size()) {
+      CopySpan(src.last(remaining.size()), remaining);
+      ivecs.back().len -= remaining.size();
+      return suffix_buf;
+    }
+    CopySpan(src, remaining.last(src.size()));
+    remaining = remaining.first(remaining.size() - src.size());
+    ivecs.back().len = 0;
+    ivecs = ivecs.first(ivecs.size() - 1);
+  }
+  return std::nullopt;
+}
+
+// GetAndRemoveOutSuffix is like |GetAndRemoveSuffix| but takes from a
+// |CRYPTO_IOVEC|'s |out| member instead.
+inline std::optional<Span<const uint8_t>> GetAndRemoveOutSuffix(
+    Span<uint8_t> out, Span<CRYPTO_IOVEC> iovecs) {
+  return GetAndRemoveSuffix<CRYPTO_IOVEC, /*ReadFromT=*/uint8_t *,
+                            /*ReadFrom=*/&CRYPTO_IOVEC::out>(out, iovecs);
+}
+
+}  // namespace iovec
+
+BSSL_NAMESPACE_END
 
 #endif  // OPENSSL_HEADER_CRYPTO_FIPSMODULE_CIPHER_INTERNAL_H
diff --git a/include/openssl/aead.h b/include/openssl/aead.h
index 97dd338..41df39a 100644
--- a/include/openssl/aead.h
+++ b/include/openssl/aead.h
@@ -246,6 +246,12 @@
 // defined in this header.
 #define EVP_AEAD_MAX_OVERHEAD 64
 
+// EVP_AEAD_MAX_OPEN_OVERHEAD contains the maximum overhead that any AEAD
+// defined in this header can remove - even if no AEAD actually generates that
+// much, as non-canonical paddings exist (such as in TLS which allows 256 bytes
+// padding + 64 bytes MAC).
+#define EVP_AEAD_MAX_OPEN_OVERHEAD 320
+
 // EVP_AEAD_DEFAULT_TAG_LENGTH is a magic value that can be passed to
 // EVP_AEAD_CTX_init to indicate that the default tag length for an AEAD should
 // be used.
@@ -391,6 +397,167 @@
     size_t nonce_len, const uint8_t *in, size_t in_len, const uint8_t *in_tag,
     size_t in_tag_len, const uint8_t *ad, size_t ad_len);
 
+// crypto_ivec_st (aka |CRYPTO_IVEC|) combines a pointer to input data with its
+// length. It is usually passed as an array of length of at most
+// |CRYPTO_IOVEC_MAX|.
+struct crypto_ivec_st {
+  const uint8_t *in;
+  size_t len;
+};
+
+// crypto_iovec_st (aka |CRYPTO_IOVEC| combines a pointer to input data and a
+// pointer to an output buffer with their common length. It is usually passed
+// as an array of length of at most |CRYPTO_IOVEC_MAX|.
+struct crypto_iovec_st {
+  // |out| and |in| must be disjoint or equal
+  uint8_t *out;
+  const uint8_t *in;
+  size_t len;
+};
+
+// CRYPTO_IOVEC_MAX is the maximum number of entries in an |CRYPTO_IOVEC| or
+// |CRYPTO_IVEC| parameter.
+#define CRYPTO_IOVEC_MAX 16
+
+// EVP_AEAD_CTX_sealv encrypts and authenticates the |in| bytes from |iovec|
+// and authenticates the |aadvec| bytes. It writes the same amount of
+// ciphertext to the |out| pointers of |iovec| and the authentication tag to
+// |out_tag|. It returns one on success and zero otherwise.
+//
+// WARNING: This is a preview API and should not be used yet. Not all AEADs
+// support it, or support may exist but be slow.
+//
+// TODO(crbug.com/383343306): remove the above note once this is implemented
+// for all ciphers.
+//
+// This function computes the same output as |EVP_AEAD_CTX_seal_scatter|, but
+// without requiring the input or output to be a contiguous buffer. The
+// individual input and output pieces are logically concatenated for a single
+// operation; their boundaries are not semantically significant and will not
+// impact the output.
+//
+// This function may be called concurrently with itself or any other seal/open
+// function on the same |EVP_AEAD_CTX|.
+//
+// Exactly |len| bytes are written to each |out| member of the |iovec|, and up
+// to |EVP_AEAD_max_overhead+extra_in_len| bytes to |out_tag|. On successful
+// return, |*out_tag_len| is set to the actual number of bytes written to
+// |out_tag|.
+//
+// The length of |nonce|, |nonce_len|, must be equal to the result of
+// |EVP_AEAD_nonce_length| for this AEAD.
+//
+// |EVP_AEAD_CTX_sealv| never results in a partial output. If |max_out_tag_len|
+// is insufficient, zero will be returned. If any error occurs, the |out|
+// members of the |iovec| and |out_tag| will be filled with zero bytes and
+// |*out_tag_len| set to zero.
+//
+// No output pointer may alias any other pointer passed to this function either
+// directly or via |iovec| and |aadvec|, with the one exception that it is
+// permitted for the same |iovec| member's |in| and |out| members to be equal
+// (in-place operation).
+//
+// |num_iovec| and |num_aadvec| must be <= |CRYPTO_IOVEC_MAX|.
+OPENSSL_EXPORT
+int EVP_AEAD_CTX_sealv(const EVP_AEAD_CTX *ctx, const CRYPTO_IOVEC *iovec,
+                       size_t num_iovec, uint8_t *out_tag, size_t *out_tag_len,
+                       size_t max_out_tag_len, const uint8_t *nonce,
+                       size_t nonce_len, const CRYPTO_IVEC *aadvec,
+                       size_t num_aadvec);
+
+// EVP_AEAD_CTX_openv authenticates the |in| bytes from |iovec| and |aadvec|,
+// and decrypts the |in| bytes to the |out| pointers of |iovec|. It returns one
+// on success and zero otherwise.
+//
+// WARNING: This is a preview API and should not be used yet. Not all AEADs
+// support it, or support may exist but be slow.
+//
+// TODO(crbug.com/383343306): remove the above note once this is implemented
+// for all ciphers.
+//
+// This function computes the same output as |EVP_AEAD_CTX_open|, but without
+// requiring the input or output to be a contiguous buffer. The individual
+// input and output pieces are logically concatenated for a single operation;
+// their boundaries are not semantically significant and will not impact the
+// output.
+//
+// This function may (and usually will) output less than the total length of
+// |iovec|. In this case, it outputs to a prefix of |iovec|'s output space,
+// then returns the length of what was actually written.
+//
+// In AEADs with a fixed-length authentication tag, the tag is treated as if it
+// were appended to the ciphertext, and successful outputs will always be
+// exactly the tag length shorter. To open with a separate, detached tag,
+// either provide it as a separate |CRYPTO_IOVEC|, or use
+// |EVP_AEAD_CTX_openv_detached|. The latter may be more convenient, one does
+// not need consider the possibility of output to the final |CRYPTO_IOVEC|.
+//
+// This function may be called concurrently with itself or any other seal/open
+// function on the same |EVP_AEAD_CTX|.
+//
+// The length of |nonce|, |nonce_len|, must be equal to the result of
+// |EVP_AEAD_nonce_length| for this AEAD.
+//
+// |EVP_AEAD_CTX_openv| never results in a partial output. If any error occurs,
+// |out| will be filled with zero bytes and |*out_len| set to zero.
+//
+// No output pointer may alias any other pointer passed to this function either
+// directly or via |iovec| and |aadvec|, with the one exception that it is
+// permitted for the same |iovec| member's |in| and |out| members to be equal
+// (in-place operation).
+//
+// |num_iovec| and |num_aadvec| must be <= |CRYPTO_IOVEC_MAX|.
+OPENSSL_EXPORT
+int EVP_AEAD_CTX_openv(const EVP_AEAD_CTX *ctx, const CRYPTO_IOVEC *iovec,
+                       size_t num_iovec, size_t *out_total_bytes,
+                       const uint8_t *nonce, size_t nonce_len,
+                       const CRYPTO_IVEC *aadvec, size_t num_aadvec);
+
+// EVP_AEAD_CTX_openv_detached authenticates the |in| bytes from |iovec| and
+// |aadvec| using |in_tag_len| bytes of authentication tag from |in_tag|. If
+// successful, it writes the plaintext of the |in| bytes to the |out| pointers
+// of |iovec|. It returns one on success and zero otherwise.
+//
+// WARNING: This is a preview API and should not be used yet. Not all AEADs
+// support it, or support may exist but be slow.
+//
+// TODO(crbug.com/383343306): remove the above note once this is implemented
+// for all ciphers.
+//
+// This function is usable with AEADs whose output can be split into a
+// ciphertext portion, with the same length as the plaintext, and a
+// fixed-length authentication tag. If the AEAD has a variable-length overhead,
+// this function will return zero. Such AEADs can only be used with
+// |EVP_AEAD_CTX_openv|.
+//
+// This function computes the same output as |EVP_AEAD_CTX_open|, but with a
+// detached tag and without requiring the input or output to be a contiguous
+// buffer. The individual input and output pieces are logically concatenated
+// for a single operation; their boundaries are not semantically significant
+// and will not impact the output.
+//
+// This function may be called concurrently with itself or any other seal/open
+// function on the same |EVP_AEAD_CTX|.
+//
+// The length of |nonce|, |nonce_len|, must be equal to the result of
+// |EVP_AEAD_nonce_length| for this AEAD.
+//
+// |EVP_AEAD_CTX_openv_detached| never results in a partial output. If any
+// error occurs, |out| will be filled with zero bytes.
+//
+// No output pointer may alias any other pointer passed to this function either
+// directly or via |iovec| and |aadvec|, with the one exception that it is
+// permitted for the same |iovec| member's |in| and |out| members to be equal
+// (in-place operation).
+//
+// |num_iovec| and |num_aadvec| must be <= |CRYPTO_IOVEC_MAX|.
+OPENSSL_EXPORT
+int EVP_AEAD_CTX_openv_detached(const EVP_AEAD_CTX *ctx,
+                                const CRYPTO_IOVEC *iovec, size_t num_iovec,
+                                const uint8_t *nonce, size_t nonce_len,
+                                const uint8_t *in_tag, size_t in_tag_len,
+                                const CRYPTO_IVEC *aadvec, size_t num_aadvec);
+
 // EVP_AEAD_CTX_aead returns the underlying AEAD for |ctx|, or NULL if one has
 // not been set.
 OPENSSL_EXPORT const EVP_AEAD *EVP_AEAD_CTX_aead(const EVP_AEAD_CTX *ctx);
diff --git a/include/openssl/base.h b/include/openssl/base.h
index d942427..54c3485 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -309,6 +309,8 @@
 typedef struct conf_value_st CONF_VALUE;
 typedef struct crypto_buffer_pool_st CRYPTO_BUFFER_POOL;
 typedef struct crypto_buffer_st CRYPTO_BUFFER;
+typedef struct crypto_ivec_st CRYPTO_IVEC;
+typedef struct crypto_iovec_st CRYPTO_IOVEC;
 typedef struct ctr_drbg_state_st CTR_DRBG_STATE;
 typedef struct dh_st DH;
 typedef struct dsa_st DSA;