GCC Code Coverage Report


Directory: ./
File: pdcom5/gnutls/SecureProcess.cpp
Date: 2024-12-29 04:08:32
Exec Total Coverage
Lines: 97 134 72.4%
Branches: 47 160 29.4%

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 62 auto wrap(const char *msg, T func, Args &&...args)
37 -> decltype(func(std::forward<Args>(args)...))
38 {
39
3/6
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 30 not taken.
✓ Branch 31 taken 2 times.
✗ Branch 45 not taken.
✓ Branch 46 taken 12 times.
62 const auto ans = func(std::forward<Args>(args)...);
40
6/24
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 12 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✓ Branch 16 taken 2 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✓ Branch 21 taken 12 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✓ Branch 26 taken 12 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
62 if (ans < 0 && gnutls_error_is_fatal(static_cast<int>(ans)))
41 throw PdCom::TlsError(msg, static_cast<int>(ans));
42 62 return ans;
43 }
44
45 struct TlsDeleter
46 {
47 12 void operator()(gnutls_session_t s)
48 {
49
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (s)
50 12 gnutls_deinit(s);
51 12 }
52 12 void operator()(gnutls_certificate_credentials_t c)
53 {
54
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (c)
55 12 gnutls_certificate_free_credentials(c);
56 12 }
57 };
58 } // namespace
59
60 12 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 12 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 40 void flush() override {}
80
81
82 bool handshake();
83 /** Close a TLS session */
84 void bye();
85 } tls_layer_;
86 };
87
88 12 SecureProcess::SecureProcess(SecureProcess::EncryptionDetails const &ed) :
89
2/4
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 11 taken 12 times.
✗ Branch 12 not taken.
12 PdCom::Process(std::make_shared<Impl>(ed, this))
90 12 {}
91
92 67 int SecureProcess::Impl::TlsLayer::read(char *const buf, int const count)
93 {
94 67 const ssize_t recv_chars = gnutls_record_recv(session_.get(), buf, count);
95
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
67 if (ex_ptr_)
96 std::rethrow_exception(ex_ptr_);
97
1/2
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
67 if (recv_chars >= 0)
98 67 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 1207 void SecureProcess::Impl::TlsLayer::write(const char *buf, size_t count)
111 {
112
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1207 times.
1207 if (count == 0)
113 return;
114 1207 const auto ans = gnutls_record_send(session_.get(), buf, count);
115
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 1207 times.
1207 if (ex_ptr_)
116 std::rethrow_exception(ex_ptr_);
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1207 times.
1207 if (ans < 0)
118 throw TlsError(
119 "SecureProcess::secureWrite() failed", static_cast<int>(ans));
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1207 times.
1207 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 12 load_certificates(SecureProcess::EncryptionDetails const &ed)
127 {
128 12 std::unique_ptr<gnutls_certificate_credentials_st, TlsDeleter> ans;
129
130 12 gnutls_certificate_credentials_t cc = nullptr;
131
1/2
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
24 wrap("gnutls_certificate_allocate_credentials() failed",
132 24 gnutls_certificate_allocate_credentials, &cc);
133 12 ans.reset(cc);
134
135 28 const auto to_datum_t = [](const std::string &s) -> gnutls_datum_t {
136 return {const_cast<unsigned char *>(
137 16 reinterpret_cast<const unsigned char *>(s.data())),
138 16 static_cast<unsigned>(s.size())};
139 };
140
1/2
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 if (!ed.server_ca_.empty()) {
141 12 const gnutls_datum_t f = to_datum_t(ed.server_ca_);
142 const auto a =
143 36 wrap("reading CA failed", gnutls_certificate_set_x509_trust_mem,
144
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
24 cc, &f, GNUTLS_X509_FMT_PEM);
145
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (a < 1)
146 throw PdCom::TlsError("Less than one CA loaded", 0);
147 }
148
5/6
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 10 times.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 10 times.
12 if (!ed.client_key_.empty() and !ed.client_cert_.empty()) {
149 2 const auto key = to_datum_t(ed.client_key_);
150 2 const auto cert = to_datum_t(ed.client_cert_);
151
1/2
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
8 wrap("importing client certificate failed",
152 6 gnutls_certificate_set_x509_key_mem, cc, &cert, &key,
153 GNUTLS_X509_FMT_PEM);
154 }
155
156 12 return ans;
157 }
158
159
160 12 SecureProcess::Impl::Impl(
161 SecureProcess::EncryptionDetails const &ed,
162 12 SecureProcess *This) :
163 PdCom::impl::Process(This),
164 host_(ed.server_hostname_),
165 cred_(load_certificates(ed)),
166
3/6
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 12 taken 12 times.
✗ Branch 13 not taken.
✓ Branch 19 taken 12 times.
✗ Branch 20 not taken.
12 tls_layer_(this, cred_.get(), host_)
167 {
168 // register our tls layer
169 12 Process::io = &tls_layer_;
170 12 }
171
172 12 SecureProcess::Impl::TlsLayer::TlsLayer(
173 IOLayer *io,
174 gnutls_certificate_credentials_t cc,
175 12 const std::string &host) :
176 12 IOLayer(io)
177 {
178 {
179 12 gnutls_session_t s = nullptr;
180
1/2
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
24 wrap("gnutls_init() failed", gnutls_init, &s,
181 24 GNUTLS_CLIENT | GNUTLS_NONBLOCK);
182 12 session_.reset(s);
183 }
184
185
1/2
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
36 wrap("gnutls_priority_set_direct failed", gnutls_priority_set_direct,
186 24 session_.get(), "NORMAL", nullptr);
187
188
189
1/2
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
12 wrap("using Certificates failed", gnutls_credentials_set, session_.get(),
190 GNUTLS_CRD_CERTIFICATE, cc);
191
192
1/2
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
24 gnutls_transport_set_push_function(
193 12 session_.get(),
194 [](gnutls_transport_ptr_t uptr, const void *data,
195 2504 size_t count) -> ssize_t {
196 1246 auto &sp = *reinterpret_cast<TlsLayer *>(uptr);
197 try {
198
1/2
✓ Branch 4 taken 1246 times.
✗ Branch 5 not taken.
1246 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
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
1246 return count;
208 1258 });
209
1/2
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
24 gnutls_transport_set_pull_function(
210 12 session_.get(),
211 [](gnutls_transport_ptr_t uptr, void *data,
212 572 size_t count) -> ssize_t {
213 280 auto &sp = *reinterpret_cast<TlsLayer *>(uptr);
214 try {
215 280 const int ans = sp.IOLayer::read(
216
1/2
✓ Branch 1 taken 280 times.
✗ Branch 2 not taken.
280 reinterpret_cast<char *>(data), count);
217
1/2
✓ Branch 0 taken 280 times.
✗ Branch 1 not taken.
280 if (ans >= 0)
218 280 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 292 });
230
1/2
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 if (!host.empty())
231
1/2
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
12 gnutls_session_set_verify_cert(session_.get(), host.c_str(), 0);
232
233
1/2
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
12 gnutls_transport_set_ptr(session_.get(), this);
234 12 }
235
236 12 bool SecureProcess::Impl::TlsLayer::handshake()
237 {
238 12 const auto a = gnutls_handshake(session_.get());
239
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 if (a == GNUTLS_E_AGAIN or a == GNUTLS_E_INTERRUPTED)
240 return false;
241
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 else if (gnutls_error_is_fatal(a))
242 throw TlsError("TLS Handshake failed", a);
243 12 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 12 bool SecureProcess::handshake()
258 {
259 12 return static_cast<Impl &>(*pimpl).tls_layer_.handshake();
260 }
261
262 31 void SecureProcess::InitLibrary()
263 {
264
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
31 if (gnutls_global_init() != GNUTLS_E_SUCCESS)
265 throw PdCom::Exception("gnults_global_init() failed");
266 31 }
267
268 31 void SecureProcess::FinalizeLibrary()
269 {
270 31 gnutls_global_deinit();
271 31 }
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