pki: add PEMDecode and PEMDecodeSingle

These helper functions use PEMTokenizer to decode PEM tokens into either
a sequence of tokens of a set of acceptable types or the body of a
single token of a known type. In practice, many users of PEMTokenizer
are doing one of these two operations.

Change-Id: I6bb613fdac7fabeb7178115fa15747b57c46e06b
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/81228
Reviewed-by: Lily Chen <chlily@google.com>
Commit-Queue: Elly FJ <ellyjones@chromium.org>
Commit-Queue: Lily Chen <chlily@google.com>
diff --git a/pki/pem.cc b/pki/pem.cc
index 4e27b74..d4687a4 100644
--- a/pki/pem.cc
+++ b/pki/pem.cc
@@ -125,6 +125,35 @@
   }
 }
 
+std::vector<PEMToken> PEMDecode(
+    std::string_view data, bssl::Span<const std::string_view> allowed_types) {
+  std::vector<PEMToken> results;
+  PEMTokenizer tokenizer(data, allowed_types);
+  while (tokenizer.GetNext()) {
+    results.push_back(PEMToken{tokenizer.block_type(), tokenizer.data()});
+  }
+  return results;
+}
+
+std::optional<std::string> PEMDecodeSingle(
+    std::string_view data, std::string_view allowed_type) {
+  const std::array<const std::string_view, 1> allowed_types = {allowed_type};
+  PEMTokenizer tokenizer(data, allowed_types);
+  if (!tokenizer.GetNext()) {
+    return std::nullopt;
+  }
+
+  std::string result = tokenizer.data();
+
+  // We need exactly one token of the allowed types, so return nullopt if
+  // there's more than one.
+  if (tokenizer.GetNext()) {
+    return std::nullopt;
+  }
+
+  return result;
+}
+
 std::string PEMEncode(std::string_view data, const std::string &type) {
   std::string b64_encoded;
   string_util::Base64Encode(data, &b64_encoded);
diff --git a/pki/pem.h b/pki/pem.h
index b7aefbf..f0e4554 100644
--- a/pki/pem.h
+++ b/pki/pem.h
@@ -17,6 +17,7 @@
 
 #include <stddef.h>
 
+#include <optional>
 #include <string>
 #include <string_view>
 #include <vector>
@@ -89,8 +90,29 @@
   std::string data_;
 };
 
-// Encodes |data| in the encapsulated message format described in RFC 1421,
-// with |type| as the PEM block type (eg: CERTIFICATE).
+// PEMToken represents a single PEM token. Headers are not stored or supported.
+struct PEMToken {
+  std::string type;
+  std::string data;
+};
+
+// PEMDecode decodes |data| into a sequence of PEM tokens. Only tokens whose
+// type appears in |allowed_types| are included in the resulting vector. The
+// resulting vector may be empty if either there are no valid PEM tokens in the
+// input, or there are valid tokens but none of them match any of the allowed
+// types.
+OPENSSL_EXPORT std::vector<PEMToken> PEMDecode(
+    std::string_view data, bssl::Span<const std::string_view> allowed_types);
+
+// PEMDecodeSingle decodes |data| into a single PEM token, which must be of the
+// specified |allowed_type|, and returns that token's base64-decoded body.
+// Returns nullopt if there is not exactly one token of the allowed type in the
+// input for any reason.
+OPENSSL_EXPORT std::optional<std::string> PEMDecodeSingle(
+    std::string_view data, std::string_view allowed_type);
+
+// PEMEncode encodes |data| in the encapsulated message format described in RFC
+// 1421, with |type| as the PEM block type (eg: CERTIFICATE).
 OPENSSL_EXPORT std::string PEMEncode(std::string_view data,
                                      const std::string &type);
 
diff --git a/pki/pem_unittest.cc b/pki/pem_unittest.cc
index 3a6e2db..c75e326 100644
--- a/pki/pem_unittest.cc
+++ b/pki/pem_unittest.cc
@@ -195,6 +195,60 @@
   EXPECT_FALSE(tokenizer.GetNext());
 }
 
+TEST(PEMDecodeTest, BasicSingle) {
+  const std::string_view data =
+      "-----BEGIN SINGLE-----\n"
+      "YmxvY2sgYm9keQ=="
+      "-----END SINGLE-----\n"
+      "-----BEGIN WRONG-----\n"
+      "d3JvbmcgYmxvY2sgYm9keQ=="
+      "-----END WRONG-----\n";
+  std::optional<std::string> result = PEMDecodeSingle(data, "SINGLE");
+  ASSERT_TRUE(result);
+  EXPECT_EQ(*result, "block body");
+}
+
+TEST(PEMDecodeTest, BasicMulti) {
+  const std::string_view data =
+      "-----BEGIN MULTI-1-----\n"
+      "YmxvY2sgYm9keSAx"
+      "-----END MULTI-1-----\n"
+      "-----BEGIN WRONG-----\n"
+      "d3JvbmcgYmxvY2sgYm9keQ=="
+      "-----END WRONG-----\n"
+      "-----BEGIN MULTI-2-----\n"
+      "YmxvY2sgYm9keSAy"
+      "-----END MULTI-2-----\n";
+  const std::array<std::string_view, 2> accepted_types = {"MULTI-1", "MULTI-2"};
+  std::vector<PEMToken> result = PEMDecode(data, accepted_types);
+  EXPECT_EQ(result.size(), 2u);
+  EXPECT_EQ(result[0].type, "MULTI-1");
+  EXPECT_EQ(result[0].data, "block body 1");
+  EXPECT_EQ(result[1].type, "MULTI-2");
+  EXPECT_EQ(result[1].data, "block body 2");
+}
+
+TEST(PEMDecodeTest, TypeMismatchSingle) {
+  const std::string_view data =
+      "-----BEGIN WRONG-----\n"
+      "d3JvbmcgYmxvY2sgYm9keQ=="
+      "-----END WRONG-----\n";
+  std::optional<std::string> result = PEMDecodeSingle(data, "SINGLE");
+  EXPECT_FALSE(result);
+}
+
+TEST(PEMDecodeTest, TooManySingle) {
+  const std::string_view data =
+      "-----BEGIN SINGLE-----\n"
+      "YmV0dGVyIG5vdCBzZWUgdGhpcw=="
+      "-----END SINGLE-----\n"
+      "-----BEGIN SINGLE-----\n"
+      "b3IgdGhpcw=="
+      "-----END SINGLE-----\n";
+  std::optional<std::string> result = PEMDecodeSingle(data, "SINGLE");
+  EXPECT_FALSE(result);
+}
+
 TEST(PEMEncodeTest, Basic) {
   EXPECT_EQ(
       "-----BEGIN BLOCK-ONE-----\n"