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