1 // Copyright 2010 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "pem.h"
6 #include "string_util.h"
7
8 #include <string_view>
9
10 namespace {
11
12 constexpr std::string_view kPEMHeaderBeginBlock = "-----BEGIN ";
13 constexpr std::string_view kPEMHeaderEndBlock = "-----END ";
14 constexpr std::string_view kPEMHeaderTail = "-----";
15
16 } // namespace
17
18 namespace bssl {
19
20
21
22 struct PEMTokenizer::PEMType {
23 std::string type;
24 std::string header;
25 std::string footer;
26 };
27
PEMTokenizer(std::string_view str,const std::vector<std::string> & allowed_block_types)28 PEMTokenizer::PEMTokenizer(
29 std::string_view str, const std::vector<std::string> &allowed_block_types) {
30 Init(str, allowed_block_types);
31 }
32
33 PEMTokenizer::~PEMTokenizer() = default;
34
GetNext()35 bool PEMTokenizer::GetNext() {
36 while (pos_ != std::string_view::npos) {
37 // Scan for the beginning of the next PEM encoded block.
38 pos_ = str_.find(kPEMHeaderBeginBlock, pos_);
39 if (pos_ == std::string_view::npos) {
40 return false; // No more PEM blocks
41 }
42
43 std::vector<PEMType>::const_iterator it;
44 // Check to see if it is of an acceptable block type.
45 for (it = block_types_.begin(); it != block_types_.end(); ++it) {
46 if (!bssl::string_util::StartsWith(str_.substr(pos_), it->header)) {
47 continue;
48 }
49
50 // Look for a footer matching the header. If none is found, then all
51 // data following this point is invalid and should not be parsed.
52 std::string_view::size_type footer_pos = str_.find(it->footer, pos_);
53 if (footer_pos == std::string_view::npos) {
54 pos_ = std::string_view::npos;
55 return false;
56 }
57
58 // Chop off the header and footer and parse the data in between.
59 std::string_view::size_type data_begin = pos_ + it->header.size();
60 pos_ = footer_pos + it->footer.size();
61 block_type_ = it->type;
62
63 std::string_view encoded =
64 str_.substr(data_begin, footer_pos - data_begin);
65 if (!string_util::Base64Decode(
66 string_util::CollapseWhitespaceASCII(encoded, true), &data_)) {
67 // The most likely cause for a decode failure is a datatype that
68 // includes PEM headers, which are not supported.
69 break;
70 }
71
72 return true;
73 }
74
75 // If the block did not match any acceptable type, move past it and
76 // continue the search. Otherwise, |pos_| has been updated to the most
77 // appropriate search position to continue searching from and should not
78 // be adjusted.
79 if (it == block_types_.end()) {
80 pos_ += kPEMHeaderBeginBlock.size();
81 }
82 }
83
84 return false;
85 }
86
Init(std::string_view str,const std::vector<std::string> & allowed_block_types)87 void PEMTokenizer::Init(std::string_view str,
88 const std::vector<std::string> &allowed_block_types) {
89 str_ = str;
90 pos_ = 0;
91
92 // Construct PEM header/footer strings for all the accepted types, to
93 // reduce parsing later.
94 for (const auto &allowed_block_type : allowed_block_types) {
95 PEMType allowed_type;
96 allowed_type.type = allowed_block_type;
97 allowed_type.header = kPEMHeaderBeginBlock;
98 allowed_type.header.append(allowed_block_type);
99 allowed_type.header.append(kPEMHeaderTail);
100 allowed_type.footer = kPEMHeaderEndBlock;
101 allowed_type.footer.append(allowed_block_type);
102 allowed_type.footer.append(kPEMHeaderTail);
103 block_types_.push_back(allowed_type);
104 }
105 }
106
PEMEncode(std::string_view data,const std::string & type)107 std::string PEMEncode(std::string_view data, const std::string &type) {
108 std::string b64_encoded;
109 string_util::Base64Encode(data, &b64_encoded);
110
111 // Divide the Base-64 encoded data into 64-character chunks, as per
112 // 4.3.2.4 of RFC 1421.
113 static const size_t kChunkSize = 64;
114 size_t chunks = (b64_encoded.size() + (kChunkSize - 1)) / kChunkSize;
115
116 std::string pem_encoded;
117 pem_encoded.reserve(
118 // header & footer
119 17 + 15 + type.size() * 2 +
120 // encoded data
121 b64_encoded.size() +
122 // newline characters for line wrapping in encoded data
123 chunks);
124
125 pem_encoded = kPEMHeaderBeginBlock;
126 pem_encoded.append(type);
127 pem_encoded.append(kPEMHeaderTail);
128 pem_encoded.append("\n");
129
130 for (size_t i = 0, chunk_offset = 0; i < chunks;
131 ++i, chunk_offset += kChunkSize) {
132 pem_encoded.append(b64_encoded, chunk_offset, kChunkSize);
133 pem_encoded.append("\n");
134 }
135
136 pem_encoded.append(kPEMHeaderEndBlock);
137 pem_encoded.append(type);
138 pem_encoded.append(kPEMHeaderTail);
139 pem_encoded.append("\n");
140 return pem_encoded;
141 }
142
143 } // namespace bssl
144