• 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 <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