1 // Copyright 2010 The Chromium Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "pem.h"
16 #include "string_util.h"
17
18 #include <array>
19 #include <string_view>
20
21 namespace {
22
23 constexpr std::string_view kPEMHeaderBeginBlock = "-----BEGIN ";
24 constexpr std::string_view kPEMHeaderEndBlock = "-----END ";
25 constexpr std::string_view kPEMHeaderTail = "-----";
26
27 } // namespace
28
29 BSSL_NAMESPACE_BEGIN
30
31
32
33 struct PEMTokenizer::PEMType {
34 std::string type;
35 std::string header;
36 std::string footer;
37 };
38
PEMTokenizer(std::string_view str,const std::vector<std::string> & allowed_block_types)39 PEMTokenizer::PEMTokenizer(
40 std::string_view str, const std::vector<std::string> &allowed_block_types) {
41 std::vector<std::string_view> types;
42 for (const auto &type : allowed_block_types) {
43 types.emplace_back(type);
44 }
45 Init(str, bssl::Span(types));
46 }
47
PEMTokenizer(std::string_view str,bssl::Span<const std::string_view> allowed_block_types)48 PEMTokenizer::PEMTokenizer(
49 std::string_view str,
50 bssl::Span<const std::string_view> allowed_block_types) {
51 Init(str, allowed_block_types);
52 }
53
54 PEMTokenizer::~PEMTokenizer() = default;
55
GetNext()56 bool PEMTokenizer::GetNext() {
57 while (pos_ != std::string_view::npos) {
58 // Scan for the beginning of the next PEM encoded block.
59 pos_ = str_.find(kPEMHeaderBeginBlock, pos_);
60 if (pos_ == std::string_view::npos) {
61 return false; // No more PEM blocks
62 }
63
64 std::vector<PEMType>::const_iterator it;
65 // Check to see if it is of an acceptable block type.
66 for (it = block_types_.begin(); it != block_types_.end(); ++it) {
67 if (!bssl::string_util::StartsWith(str_.substr(pos_), it->header)) {
68 continue;
69 }
70
71 // Look for a footer matching the header. If none is found, then all
72 // data following this point is invalid and should not be parsed.
73 std::string_view::size_type footer_pos = str_.find(it->footer, pos_);
74 if (footer_pos == std::string_view::npos) {
75 pos_ = std::string_view::npos;
76 return false;
77 }
78
79 // Chop off the header and footer and parse the data in between.
80 std::string_view::size_type data_begin = pos_ + it->header.size();
81 pos_ = footer_pos + it->footer.size();
82 block_type_ = it->type;
83
84 std::string_view encoded =
85 str_.substr(data_begin, footer_pos - data_begin);
86 if (!string_util::Base64Decode(
87 string_util::CollapseWhitespaceASCII(encoded, true), &data_)) {
88 // The most likely cause for a decode failure is a datatype that
89 // includes PEM headers, which are not supported.
90 break;
91 }
92
93 return true;
94 }
95
96 // If the block did not match any acceptable type, move past it and
97 // continue the search. Otherwise, |pos_| has been updated to the most
98 // appropriate search position to continue searching from and should not
99 // be adjusted.
100 if (it == block_types_.end()) {
101 pos_ += kPEMHeaderBeginBlock.size();
102 }
103 }
104
105 return false;
106 }
107
Init(std::string_view str,bssl::Span<const std::string_view> allowed_block_types)108 void PEMTokenizer::Init(std::string_view str,
109 bssl::Span<const std::string_view> allowed_block_types) {
110 str_ = str;
111 pos_ = 0;
112
113 // Construct PEM header/footer strings for all the accepted types, to
114 // reduce parsing later.
115 for (const auto &allowed_block_type : allowed_block_types) {
116 PEMType allowed_type;
117 allowed_type.type = allowed_block_type;
118 allowed_type.header = kPEMHeaderBeginBlock;
119 allowed_type.header.append(allowed_block_type);
120 allowed_type.header.append(kPEMHeaderTail);
121 allowed_type.footer = kPEMHeaderEndBlock;
122 allowed_type.footer.append(allowed_block_type);
123 allowed_type.footer.append(kPEMHeaderTail);
124 block_types_.push_back(allowed_type);
125 }
126 }
127
PEMDecode(std::string_view data,bssl::Span<const std::string_view> allowed_types)128 std::vector<PEMToken> PEMDecode(
129 std::string_view data, bssl::Span<const std::string_view> allowed_types) {
130 std::vector<PEMToken> results;
131 PEMTokenizer tokenizer(data, allowed_types);
132 while (tokenizer.GetNext()) {
133 results.push_back(PEMToken{tokenizer.block_type(), tokenizer.data()});
134 }
135 return results;
136 }
137
PEMDecodeSingle(std::string_view data,std::string_view allowed_type)138 std::optional<std::string> PEMDecodeSingle(
139 std::string_view data, std::string_view allowed_type) {
140 const std::array<const std::string_view, 1> allowed_types = {allowed_type};
141 PEMTokenizer tokenizer(data, allowed_types);
142 if (!tokenizer.GetNext()) {
143 return std::nullopt;
144 }
145
146 std::string result = tokenizer.data();
147
148 // We need exactly one token of the allowed types, so return nullopt if
149 // there's more than one.
150 if (tokenizer.GetNext()) {
151 return std::nullopt;
152 }
153
154 return result;
155 }
156
PEMEncode(std::string_view data,const std::string & type)157 std::string PEMEncode(std::string_view data, const std::string &type) {
158 std::string b64_encoded;
159 string_util::Base64Encode(data, &b64_encoded);
160
161 // Divide the Base-64 encoded data into 64-character chunks, as per
162 // 4.3.2.4 of RFC 1421.
163 static const size_t kChunkSize = 64;
164 size_t chunks = (b64_encoded.size() + (kChunkSize - 1)) / kChunkSize;
165
166 std::string pem_encoded;
167 pem_encoded.reserve(
168 // header & footer
169 17 + 15 + type.size() * 2 +
170 // encoded data
171 b64_encoded.size() +
172 // newline characters for line wrapping in encoded data
173 chunks);
174
175 pem_encoded = kPEMHeaderBeginBlock;
176 pem_encoded.append(type);
177 pem_encoded.append(kPEMHeaderTail);
178 pem_encoded.append("\n");
179
180 for (size_t i = 0, chunk_offset = 0; i < chunks;
181 ++i, chunk_offset += kChunkSize) {
182 pem_encoded.append(b64_encoded, chunk_offset, kChunkSize);
183 pem_encoded.append("\n");
184 }
185
186 pem_encoded.append(kPEMHeaderEndBlock);
187 pem_encoded.append(type);
188 pem_encoded.append(kPEMHeaderTail);
189 pem_encoded.append("\n");
190 return pem_encoded;
191 }
192
193 BSSL_NAMESPACE_END
194