1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // 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, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_tls_client/test/test_server.h"
16
17 #include <cstring>
18
19 #include "pw_log/log.h"
20 #include "pw_result/result.h"
21 #include "pw_status/status.h"
22 #include "pw_stream/stream.h"
23
24 namespace {
25
TestBioNew(BIO * bio)26 int TestBioNew(BIO* bio) {
27 bio->init = 1;
28 return 1;
29 }
30
TestBioCtrl(BIO *,int,long,void *)31 long TestBioCtrl(BIO*, int, long, void*) { return 1; }
32
TestBioFree(BIO *)33 int TestBioFree(BIO*) { return 1; }
34
35 } // namespace
36
37 namespace pw::tls_client::test {
38
ParseDerCertificate(ConstByteSpan cert)39 Result<X509*> ParseDerCertificate(ConstByteSpan cert) {
40 BIO* bio = BIO_new_mem_buf(cert.data(), cert.size());
41 if (!bio) {
42 return Status::Internal();
43 }
44
45 X509* x509 = d2i_X509_bio(bio, nullptr);
46 if (x509 == nullptr) {
47 PW_LOG_DEBUG("Failed to parse x509");
48 BIO_free(bio);
49 return Status::Internal();
50 }
51
52 BIO_free(bio);
53 return x509;
54 }
55
DoRead(ByteSpan dest)56 StatusWithSize FixedSizeFIFOBuffer::DoRead(ByteSpan dest) {
57 size_t to_read = std::min(current_size_, dest.size());
58 memcpy(dest.data(), buffer_.data(), to_read);
59 // Push out the read data.
60 memmove(buffer_.data(), buffer_.data() + to_read, current_size_ - to_read);
61 current_size_ -= to_read;
62 return StatusWithSize(to_read);
63 }
64
DoWrite(ConstByteSpan data)65 Status FixedSizeFIFOBuffer::DoWrite(ConstByteSpan data) {
66 if (data.size() + current_size_ > buffer_.size()) {
67 PW_LOG_ERROR("Write overflow. Buffer is too small.");
68 return Status::ResourceExhausted();
69 }
70
71 // Append incoming data at the end.
72 memcpy(buffer_.begin() + current_size_, data.data(), data.size());
73 current_size_ += data.size();
74 return OkStatus();
75 }
76
InMemoryTestServer(ByteSpan input_buffer,ByteSpan output_buffer)77 InMemoryTestServer::InMemoryTestServer(ByteSpan input_buffer,
78 ByteSpan output_buffer)
79 : input_buffer_(input_buffer), output_buffer_(output_buffer) {}
80
BioRead(BIO * bio,char * out,int output_length)81 int InMemoryTestServer::BioRead(BIO* bio, char* out, int output_length) {
82 auto server = static_cast<InMemoryTestServer*>(bio->ptr);
83 auto read = server->input_buffer_.Read(
84 as_writable_bytes(span{out, static_cast<size_t>(output_length)}));
85 if (!read.ok()) {
86 server->last_bio_status_ = read.status();
87 return -1;
88 }
89 if (read.value().empty()) {
90 BIO_set_retry_read(bio);
91 return -1;
92 }
93 return static_cast<int>(read.value().size());
94 }
95
BioWrite(BIO * bio,const char * input,int input_length)96 int InMemoryTestServer::BioWrite(BIO* bio,
97 const char* input,
98 int input_length) {
99 auto server = static_cast<InMemoryTestServer*>(bio->ptr);
100 if (auto status = server->output_buffer_.Write(
101 as_bytes(span{input, static_cast<size_t>(input_length)}));
102 !status.ok()) {
103 server->last_bio_status_ = status;
104 return -1;
105 }
106
107 return input_length;
108 }
109
Initialize(ConstByteSpan key,ConstByteSpan cert,span<const ConstByteSpan> chains)110 Status InMemoryTestServer::Initialize(ConstByteSpan key,
111 ConstByteSpan cert,
112 span<const ConstByteSpan> chains) {
113 input_buffer_.clear();
114 output_buffer_.clear();
115 is_handshake_done_ = false;
116
117 ctx_ = bssl::UniquePtr<SSL_CTX>(SSL_CTX_new(TLS_method()));
118 if (!ctx_) {
119 return Status::Internal();
120 }
121
122 if (auto status = LoadPrivateKey(key); !status.ok()) {
123 return status;
124 }
125
126 if (auto status = LoadCertificate(cert); !status.ok()) {
127 return status;
128 }
129
130 if (auto status = LoadCAChain(chains); !status.ok()) {
131 return status;
132 }
133
134 ssl_ = bssl::UniquePtr<SSL>(SSL_new(ctx_.get()));
135 if (!ssl_) {
136 return Status::Internal();
137 }
138
139 static const BIO_METHOD bio_method = {
140 BIO_TYPE_MEM,
141 "bio boringssl test server",
142 InMemoryTestServer::BioWrite,
143 InMemoryTestServer::BioRead,
144 nullptr, // puts
145 nullptr, // gets
146 TestBioCtrl, // ctrl
147 TestBioNew,
148 TestBioFree, // free
149 nullptr // callback_ctrl
150 };
151
152 BIO* bio = BIO_new(&bio_method);
153 if (!bio) {
154 return Status::Internal();
155 }
156
157 bio->ptr = this;
158
159 SSL_set_bio(ssl_.get(), bio, bio);
160 return OkStatus();
161 }
162
LoadPrivateKey(ConstByteSpan key)163 Status InMemoryTestServer::LoadPrivateKey(ConstByteSpan key) {
164 BIO* bio = BIO_new_mem_buf(key.data(), key.size());
165 if (!bio) {
166 return Status::Internal();
167 }
168
169 // Requires PEM format.
170 EVP_PKEY* pkey = d2i_PrivateKey_bio(bio, nullptr);
171 int ret = SSL_CTX_use_PrivateKey(ctx_.get(), pkey);
172 if (ret != 1) {
173 BIO_free(bio);
174 PW_LOG_DEBUG("Failed to load private key for test server");
175 return Status::Internal();
176 }
177
178 EVP_PKEY_free(pkey);
179 BIO_free(bio);
180 return OkStatus();
181 }
182
LoadCertificate(ConstByteSpan cert)183 Status InMemoryTestServer::LoadCertificate(ConstByteSpan cert) {
184 auto res = ParseDerCertificate(cert);
185 if (!res.ok()) {
186 return res.status();
187 }
188
189 int ret = SSL_CTX_use_certificate(ctx_.get(), res.value());
190 if (ret != 1) {
191 X509_free(res.value());
192 PW_LOG_DEBUG("Failed to user server certificate %d", ret);
193 return Status::Internal();
194 }
195
196 X509_free(res.value());
197 return OkStatus();
198 }
199
LoadCAChain(span<const ConstByteSpan> chains)200 Status InMemoryTestServer::LoadCAChain(span<const ConstByteSpan> chains) {
201 for (auto cert : chains) {
202 auto res = ParseDerCertificate(cert);
203 if (!res.ok()) {
204 return res.status();
205 }
206
207 int ret = SSL_CTX_add0_chain_cert(ctx_.get(), res.value());
208 if (ret != 1) {
209 X509_free(res.value());
210 PW_LOG_DEBUG("Failed to add certificate to chain %d", ret);
211 return Status::Internal();
212 }
213 }
214 return OkStatus();
215 }
216
ClientShutdownReceived()217 bool InMemoryTestServer::ClientShutdownReceived() {
218 return SSL_get_shutdown(ssl_.get()) & SSL_RECEIVED_SHUTDOWN;
219 }
220
ProcessPackets()221 Status InMemoryTestServer::ProcessPackets() {
222 // Process handshake if it has not been completed.
223 if (!is_handshake_done_) {
224 int ret = SSL_accept(ssl_.get());
225 if (ret != 1) {
226 int ssl_err = SSL_get_error(ssl_.get(), ret);
227 if (ssl_err != SSL_ERROR_WANT_READ) {
228 PW_LOG_DEBUG("Error while server accepting, %d, %d", ssl_err, ret);
229 return Status::Internal();
230 }
231 } else {
232 // handshake complete.
233 is_handshake_done_ = true;
234 }
235 }
236
237 // Hanshake may be completed above and there may already be application data.
238 if (is_handshake_done_) {
239 static std::array<std::byte, 1024> buf;
240 while (true) {
241 int ssl_ret = SSL_read(ssl_.get(), buf.data(), buf.size());
242
243 if (ssl_ret == 0) {
244 // All input has been processed.
245 break;
246 }
247
248 if (ssl_ret < 0) {
249 // An error may have occured.
250 int ssl_err = SSL_get_error(ssl_.get(), ssl_ret);
251 if (ssl_err == SSL_ERROR_WANT_READ) {
252 // Need to wait for client to finish sending data. Non-blocking
253 // return.
254 break;
255 }
256
257 PW_LOG_DEBUG("Error while server reading");
258 return Status::Internal();
259 }
260
261 // Echo the message
262 int write_status = SSL_write(ssl_.get(), buf.data(), ssl_ret);
263 if (write_status <= 0) {
264 PW_LOG_DEBUG("Failed to write for test server");
265 return Status::Internal();
266 }
267 }
268 }
269
270 return OkStatus();
271 }
272
DoRead(ByteSpan dest)273 StatusWithSize InMemoryTestServer::DoRead(ByteSpan dest) {
274 auto res = output_buffer_.Read(dest);
275 if (!res.ok()) {
276 return StatusWithSize(res.status(), 0);
277 }
278
279 return StatusWithSize(res.value().size());
280 }
281
DoWrite(ConstByteSpan data)282 Status InMemoryTestServer::DoWrite(ConstByteSpan data) {
283 auto status = input_buffer_.Write(data);
284 if (!status.ok()) {
285 return status;
286 }
287
288 // Invoke the server to process the data.
289 return ProcessPackets();
290 }
291
292 } // namespace pw::tls_client::test
293