GCC Code Coverage Report


Directory: ./
File: gnutls/SecureProcess.cpp
Date: 2024-03-27 13:09:52
Exec Total Coverage
Lines: 0 127 0.0%
Branches: 0 160 0.0%

Line Branch Exec Source
1 /*****************************************************************************
2 * vim:tw=78
3 *
4 * Copyright (C) 2021 Bjarne von Horn (vh at igh dot de).
5 *
6 * This file is part of the PdCom library.
7 *
8 * The PdCom library is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * The PdCom library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
16 * License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with the PdCom library. If not, see <http://www.gnu.org/licenses/>.
20 *
21 *****************************************************************************/
22
23 #include "../src/Process.h"
24
25 #include <algorithm>
26 #include <cstring>
27 #include <exception>
28 #include <gnutls/gnutls.h>
29 #include <pdcom5/Exception.h>
30 #include <pdcom5/SecureProcess.h>
31
32 using PdCom::SecureProcess;
33
34 namespace {
35 template <typename T, typename... Args>
36 auto wrap(const char *msg, T func, Args &&...args)
37 -> decltype(func(std::forward<Args>(args)...))
38 {
39 const auto ans = func(std::forward<Args>(args)...);
40 if (ans < 0 && gnutls_error_is_fatal(static_cast<int>(ans)))
41 throw PdCom::TlsError(msg, static_cast<int>(ans));
42 return ans;
43 }
44
45 struct TlsDeleter
46 {
47 void operator()(gnutls_session_t s)
48 {
49 if (s)
50 gnutls_deinit(s);
51 }
52 void operator()(gnutls_certificate_credentials_t c)
53 {
54 if (c)
55 gnutls_certificate_free_credentials(c);
56 }
57 };
58 } // namespace
59
60 struct PDCOM5_LOCAL SecureProcess::Impl final : public PdCom::impl::Process
61 {
62 Impl(SecureProcess::EncryptionDetails const &ed, SecureProcess *This);
63 std::string host_;
64 std::unique_ptr<gnutls_certificate_credentials_st, TlsDeleter> cred_;
65
66 struct TlsLayer : PdCom::impl::IOLayer
67 {
68 std::unique_ptr<gnutls_session_int, TlsDeleter> session_;
69 std::exception_ptr ex_ptr_;
70
71 TlsLayer(
72 IOLayer *,
73 gnutls_certificate_credentials_t cc,
74 const std::string &host);
75
76 // from IOLayer
77 void write(const char *buf, size_t count) override;
78 int read(char *buf, int count) override;
79 void flush() override {}
80
81
82 bool handshake();
83 /** Close a TLS session */
84 void bye();
85 } tls_layer_;
86 };
87
88 SecureProcess::SecureProcess(SecureProcess::EncryptionDetails const &ed) :
89 PdCom::Process(std::make_shared<Impl>(ed, this))
90 {}
91
92 int SecureProcess::Impl::TlsLayer::read(char *const buf, int const count)
93 {
94 const ssize_t recv_chars = gnutls_record_recv(session_.get(), buf, count);
95 if (ex_ptr_)
96 std::rethrow_exception(ex_ptr_);
97 if (recv_chars >= 0)
98 return static_cast<int>(recv_chars);
99
100 if (recv_chars == GNUTLS_E_REHANDSHAKE) {
101 handshake();
102 return -EAGAIN;
103 }
104 if (!gnutls_error_is_fatal(static_cast<int>(recv_chars))) {
105 return -EAGAIN;
106 }
107 throw TlsError("gnutls_record_recv() failed", recv_chars);
108 }
109
110 void SecureProcess::Impl::TlsLayer::write(const char *buf, size_t count)
111 {
112 if (count == 0)
113 return;
114 const auto ans = gnutls_record_send(session_.get(), buf, count);
115 if (ex_ptr_)
116 std::rethrow_exception(ex_ptr_);
117 if (ans < 0)
118 throw TlsError(
119 "SecureProcess::secureWrite() failed", static_cast<int>(ans));
120 if (static_cast<size_t>(ans) != count)
121 throw TlsError("short write in SecureProcess::secureWrite", 0);
122 }
123
124
125 static std::unique_ptr<gnutls_certificate_credentials_st, TlsDeleter>
126 load_certificates(SecureProcess::EncryptionDetails const &ed)
127 {
128 std::unique_ptr<gnutls_certificate_credentials_st, TlsDeleter> ans;
129
130 gnutls_certificate_credentials_t cc = nullptr;
131 wrap("gnutls_certificate_allocate_credentials() failed",
132 gnutls_certificate_allocate_credentials, &cc);
133 ans.reset(cc);
134
135 const auto to_datum_t = [](const std::string &s) -> gnutls_datum_t {
136 return {const_cast<unsigned char *>(
137 reinterpret_cast<const unsigned char *>(s.data())),
138 static_cast<unsigned>(s.size())};
139 };
140 if (!ed.server_ca_.empty()) {
141 const gnutls_datum_t f = to_datum_t(ed.server_ca_);
142 const auto a =
143 wrap("reading CA failed", gnutls_certificate_set_x509_trust_mem,
144 cc, &f, GNUTLS_X509_FMT_PEM);
145 if (a < 1)
146 throw PdCom::TlsError("Less than one CA loaded", 0);
147 }
148 if (!ed.client_key_.empty() and !ed.client_cert_.empty()) {
149 const auto key = to_datum_t(ed.client_key_);
150 const auto cert = to_datum_t(ed.client_cert_);
151 wrap("importing client certificate failed",
152 gnutls_certificate_set_x509_key_mem, cc, &cert, &key,
153 GNUTLS_X509_FMT_PEM);
154 }
155
156 return ans;
157 }
158
159
160 SecureProcess::Impl::Impl(
161 SecureProcess::EncryptionDetails const &ed,
162 SecureProcess *This) :
163 PdCom::impl::Process(This),
164 host_(ed.server_hostname_),
165 cred_(load_certificates(ed)),
166 tls_layer_(this, cred_.get(), host_)
167 {
168 // register our tls layer
169 Process::io = &tls_layer_;
170 }
171
172 SecureProcess::Impl::TlsLayer::TlsLayer(
173 IOLayer *io,
174 gnutls_certificate_credentials_t cc,
175 const std::string &host) :
176 IOLayer(io)
177 {
178 {
179 gnutls_session_t s = nullptr;
180 wrap("gnutls_init() failed", gnutls_init, &s,
181 GNUTLS_CLIENT | GNUTLS_NONBLOCK);
182 session_.reset(s);
183 }
184
185 wrap("gnutls_priority_set_direct failed", gnutls_priority_set_direct,
186 session_.get(), "NORMAL", nullptr);
187
188
189 wrap("using Certificates failed", gnutls_credentials_set, session_.get(),
190 GNUTLS_CRD_CERTIFICATE, cc);
191
192 gnutls_transport_set_push_function(
193 session_.get(),
194 [](gnutls_transport_ptr_t uptr, const void *data,
195 size_t count) -> ssize_t {
196 auto &sp = *reinterpret_cast<TlsLayer *>(uptr);
197 try {
198 sp.IOLayer::write(
199 reinterpret_cast<const char *>(data), count);
200 }
201 catch (std::exception const &e) {
202 sp.ex_ptr_ = std::current_exception();
203 // make sure that errno != EAGAIN;
204 errno = EIO;
205 return -1;
206 }
207 return count;
208 });
209 gnutls_transport_set_pull_function(
210 session_.get(),
211 [](gnutls_transport_ptr_t uptr, void *data,
212 size_t count) -> ssize_t {
213 auto &sp = *reinterpret_cast<TlsLayer *>(uptr);
214 try {
215 const int ans = sp.IOLayer::read(
216 reinterpret_cast<char *>(data), count);
217 if (ans >= 0)
218 return ans;
219 if (ans == -EAGAIN)
220 errno = EAGAIN;
221 return -1;
222 }
223 catch (std::exception const &e) {
224 sp.ex_ptr_ = std::current_exception();
225 // make sure that errno != EAGAIN;
226 errno = EIO;
227 return -1;
228 }
229 });
230 if (!host.empty())
231 gnutls_session_set_verify_cert(session_.get(), host.c_str(), 0);
232
233 gnutls_transport_set_ptr(session_.get(), this);
234 }
235
236 bool SecureProcess::Impl::TlsLayer::handshake()
237 {
238 const auto a = gnutls_handshake(session_.get());
239 if (a == GNUTLS_E_AGAIN or a == GNUTLS_E_INTERRUPTED)
240 return false;
241 else if (gnutls_error_is_fatal(a))
242 throw TlsError("TLS Handshake failed", a);
243 return true;
244 }
245
246 void SecureProcess::Impl::TlsLayer::bye()
247 {
248 gnutls_bye(session_.get(), GNUTLS_SHUT_RDWR);
249 }
250
251
252 void SecureProcess::bye()
253 {
254 return static_cast<Impl &>(*pimpl).tls_layer_.bye();
255 }
256
257 bool SecureProcess::handshake()
258 {
259 return static_cast<Impl &>(*pimpl).tls_layer_.handshake();
260 }
261
262 void SecureProcess::InitLibrary()
263 {
264 if (gnutls_global_init() != GNUTLS_E_SUCCESS)
265 throw PdCom::Exception("gnults_global_init() failed");
266 }
267
268 void SecureProcess::FinalizeLibrary()
269 {
270 gnutls_global_deinit();
271 }
272
273 void SecureProcess::flush()
274 {}
275
276 void SecureProcess::asyncData()
277 {
278 do {
279 Process::asyncData();
280 } while (gnutls_record_check_pending(
281 static_cast<Impl &>(*pimpl).tls_layer_.session_.get()));
282 }
283