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