blob: af9d1937d408131ac19871f92b53c0b41c9cdf77 [file] [log] [blame]
Bob Beckbc97b7a2023-04-18 08:35:15 -06001// 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 "string_util.h"
6#include "pem.h"
7
8#include "fillins/base64.h"
9#include <string_view>
10
11#include "fillins/string_util.h"
12
13namespace {
14
15constexpr std::string_view kPEMHeaderBeginBlock = "-----BEGIN ";
16constexpr std::string_view kPEMHeaderEndBlock = "-----END ";
17constexpr std::string_view kPEMHeaderTail = "-----";
18
19} // namespace
20
21namespace bssl {
22
23
24
25struct PEMTokenizer::PEMType {
26 std::string type;
27 std::string header;
28 std::string footer;
29};
30
31PEMTokenizer::PEMTokenizer(
32 std::string_view str,
33 const std::vector<std::string>& allowed_block_types) {
34 Init(str, allowed_block_types);
35}
36
37PEMTokenizer::~PEMTokenizer() = default;
38
39bool PEMTokenizer::GetNext() {
40 while (pos_ != std::string_view::npos) {
41 // Scan for the beginning of the next PEM encoded block.
42 pos_ = str_.find(kPEMHeaderBeginBlock, pos_);
43 if (pos_ == std::string_view::npos)
44 return false; // No more PEM blocks
45
46 std::vector<PEMType>::const_iterator it;
47 // Check to see if it is of an acceptable block type.
48 for (it = block_types_.begin(); it != block_types_.end(); ++it) {
49 if (!bssl::string_util::StartsWith(str_.substr(pos_), it->header))
50 continue;
51
52 // Look for a footer matching the header. If none is found, then all
53 // data following this point is invalid and should not be parsed.
54 std::string_view::size_type footer_pos = str_.find(it->footer, pos_);
55 if (footer_pos == std::string_view::npos) {
56 pos_ = std::string_view::npos;
57 return false;
58 }
59
60 // Chop off the header and footer and parse the data in between.
61 std::string_view::size_type data_begin = pos_ + it->header.size();
62 pos_ = footer_pos + it->footer.size();
63 block_type_ = it->type;
64
65 std::string_view encoded = str_.substr(data_begin, footer_pos - data_begin);
66 if (!fillins::Base64Decode(fillins::CollapseWhitespaceASCII(encoded, true),
67 &data_)) {
68 // The most likely cause for a decode failure is a datatype that
69 // includes PEM headers, which are not supported.
70 break;
71 }
72
73 return true;
74 }
75
76 // If the block did not match any acceptable type, move past it and
77 // continue the search. Otherwise, |pos_| has been updated to the most
78 // appropriate search position to continue searching from and should not
79 // be adjusted.
80 if (it == block_types_.end())
81 pos_ += kPEMHeaderBeginBlock.size();
82 }
83
84 return false;
85}
86
87void 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
107std::string PEMEncode(std::string_view data, const std::string& type) {
108 std::string b64_encoded;
109 fillins::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 net