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"