1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25
26 #include <cfnet.h>
27
28 #include <openssl/err.h>
29 #include <openssl/crypto.h>
30 #include <openssl/ssl.h>
31
32 #include <logging.h>
33 #include <misc_lib.h>
34
35 #include <tls_client.h>
36 #include <tls_generic.h>
37 #include <net.h> /* SendTransaction, ReceiveTransaction */
38 #include <protocol.h> /* ParseProtocolVersionNetwork() */
39 /* TODO move crypto.h to libutils */
40 #include <crypto.h> /* LoadSecretKeys */
41
42 #define MAX_CONNECT_RETRIES 10
43
44 extern RSA *PRIVKEY, *PUBKEY;
45
46
47 /**
48 * Global SSL context for initiated connections over the TLS protocol. For the
49 * agent, they are written only once, *after* the common control bundles have
50 * been evaluated. I.e. GenericAgentPostLoadInit() is called
51 * after LoadPolicy().
52 *
53 * 1. Common bundles evaluation: LoadPolicy()->PolicyResolve()
54 * 2. Create TLS contexts: GenericAgentPostLoadInit()->cfnet_init()
55 */
56 static SSL_CTX *SSLCLIENTCONTEXT = NULL;
57 static X509 *SSLCLIENTCERT = NULL;
58
59
TLSClientIsInitialized()60 bool TLSClientIsInitialized()
61 {
62 return (SSLCLIENTCONTEXT != NULL);
63 }
64
65 /**
66 * @warning Make sure you've called CryptoInitialize() first!
67 *
68 * @TODO if this function is called a second time, it just returns true, and
69 * does not do nothing more. What if the settings (e.g. tls_min_version) have
70 * changed? This can happen when cf-serverd reloads policy. Fixing this goes
71 * much deeper though, as it would require cf-serverd to call
72 * GenericAgentDiscoverContext() when reloading policy.
73 */
TLSClientInitialize(const char * tls_min_version,const char * ciphers)74 bool TLSClientInitialize(const char *tls_min_version,
75 const char *ciphers)
76 {
77 int ret;
78 static bool is_initialised = false;
79
80 if (is_initialised)
81 {
82 return true;
83 }
84
85 if (PRIVKEY == NULL || PUBKEY == NULL)
86 {
87 /* VERBOSE in case it's a custom, local-only installation. */
88 Log(LOG_LEVEL_VERBOSE, "No public/private key pair is loaded,"
89 " please create one using cf-key");
90 return false;
91 }
92 if (!TLSGenericInitialize())
93 {
94 return false;
95 }
96
97 SSLCLIENTCONTEXT = SSL_CTX_new(SSLv23_client_method());
98 if (SSLCLIENTCONTEXT == NULL)
99 {
100 Log(LOG_LEVEL_ERR, "SSL_CTX_new: %s",
101 TLSErrorString(ERR_get_error()));
102 goto err1;
103 }
104
105 TLSSetDefaultOptions(SSLCLIENTCONTEXT, tls_min_version);
106
107 if (!TLSSetCipherList(SSLCLIENTCONTEXT, ciphers))
108 {
109 goto err2;
110 }
111
112 /* Create cert into memory and load it into SSL context. */
113 SSLCLIENTCERT = TLSGenerateCertFromPrivKey(PRIVKEY);
114 if (SSLCLIENTCERT == NULL)
115 {
116 Log(LOG_LEVEL_ERR,
117 "Failed to generate in-memory-certificate from private key");
118 goto err2;
119 }
120
121 SSL_CTX_use_certificate(SSLCLIENTCONTEXT, SSLCLIENTCERT);
122
123 ret = SSL_CTX_use_RSAPrivateKey(SSLCLIENTCONTEXT, PRIVKEY);
124 if (ret != 1)
125 {
126 Log(LOG_LEVEL_ERR, "Failed to use RSA private key: %s",
127 TLSErrorString(ERR_get_error()));
128 goto err3;
129 }
130
131 /* Verify cert consistency. */
132 ret = SSL_CTX_check_private_key(SSLCLIENTCONTEXT);
133 if (ret != 1)
134 {
135 Log(LOG_LEVEL_ERR, "Inconsistent key and TLS cert: %s",
136 TLSErrorString(ERR_get_error()));
137 goto err3;
138 }
139
140 is_initialised = true;
141 return true;
142
143 err3:
144 X509_free(SSLCLIENTCERT);
145 SSLCLIENTCERT = NULL;
146 err2:
147 SSL_CTX_free(SSLCLIENTCONTEXT);
148 SSLCLIENTCONTEXT = NULL;
149 err1:
150 return false;
151 }
152
TLSDeInitialize()153 void TLSDeInitialize()
154 {
155 if (PUBKEY)
156 {
157 RSA_free(PUBKEY);
158 PUBKEY = NULL;
159 }
160
161 if (PRIVKEY)
162 {
163 RSA_free(PRIVKEY);
164 PRIVKEY = NULL;
165 }
166
167 if (SSLCLIENTCERT != NULL)
168 {
169 X509_free(SSLCLIENTCERT);
170 SSLCLIENTCERT = NULL;
171 }
172
173 if (SSLCLIENTCONTEXT != NULL)
174 {
175 SSL_CTX_free(SSLCLIENTCONTEXT);
176 SSLCLIENTCONTEXT = NULL;
177 }
178 }
179
180
181 /**
182 * 1. Receive "CFE_v%d" server hello
183 * 2. Send two lines: one "CFE_v%d" with the protocol version we wish to have,
184 * and another with id, e.g. "IDENTITY USERNAME=blah".
185 * 3. Receive "OK WELCOME"
186 *
187 * @return > 0: success. #conn_info->type has been updated with the negotiated
188 * protocol version.
189 * 0: server denial
190 * -1: error
191 */
TLSClientIdentificationDialog(ConnectionInfo * conn_info,const char * username)192 int TLSClientIdentificationDialog(ConnectionInfo *conn_info,
193 const char *username)
194 {
195 char line[1024] = "";
196 int ret;
197
198 /* Receive CFE_v%d ... That's the first thing the server sends. */
199 ret = TLSRecvLines(conn_info->ssl, line, sizeof(line));
200 if (ret == -1)
201 {
202 Log(LOG_LEVEL_ERR, "Connection was hung up during identification! (0)");
203 return -1;
204 }
205
206 ProtocolVersion wanted_version;
207 if (conn_info->protocol == CF_PROTOCOL_UNDEFINED)
208 {
209 wanted_version = CF_PROTOCOL_LATEST;
210 }
211 else
212 {
213 wanted_version = conn_info->protocol;
214 }
215
216 const ProtocolVersion received_version = ParseProtocolVersionNetwork(line);
217
218 if (received_version < wanted_version && ProtocolIsTLS(received_version))
219 {
220 // Downgrade as long as it's still TLS
221 wanted_version = received_version;
222 }
223 else if (ProtocolIsUndefined(received_version)
224 || ProtocolIsClassic(received_version))
225 {
226 Log(LOG_LEVEL_ERR, "Server sent a bad version number! (0a)");
227 return -1;
228 }
229
230 assert(wanted_version <= received_version); // Server supported version
231 assert(ProtocolIsTLS(wanted_version));
232
233 /* Send "CFE_v%d cf-agent version". */
234 char version_string[128];
235 int len = snprintf(version_string, sizeof(version_string),
236 "CFE_v%d %s %s\n",
237 wanted_version, "cf-agent", VERSION); /* TODO argv[0] */
238
239 ret = TLSSend(conn_info->ssl, version_string, len);
240 if (ret != len)
241 {
242 Log(LOG_LEVEL_ERR, "Connection was hung up during identification! (1)");
243 return -1;
244 }
245
246 strcpy(line, "IDENTITY");
247 size_t line_len = strlen(line);
248
249 if (username != NULL)
250 {
251 ret = snprintf(&line[line_len], sizeof(line) - line_len,
252 " USERNAME=%s", username);
253 if (ret < 0)
254 {
255 Log(LOG_LEVEL_ERR, "snprintf failed: %s", GetErrorStr());
256 return -1;
257 }
258 else if ((unsigned int) ret >= sizeof(line) - line_len)
259 {
260 Log(LOG_LEVEL_ERR, "Sending IDENTITY truncated: %s", line);
261 return -1;
262 }
263 line_len += ret;
264 }
265
266 /* Overwrite the terminating '\0', we don't need it anyway. */
267 line[line_len] = '\n';
268 line_len++;
269
270 ret = TLSSend(conn_info->ssl, line, line_len);
271 if (ret == -1)
272 {
273 Log(LOG_LEVEL_ERR,
274 "Connection was hung up during identification! (2)");
275 return -1;
276 }
277
278 /* Server might hang up here, after we sent identification! We
279 * must get the "OK WELCOME" message for everything to be OK. */
280 static const char OK[] = "OK WELCOME";
281 size_t OK_len = sizeof(OK) - 1;
282 ret = TLSRecvLines(conn_info->ssl, line, sizeof(line));
283 if (ret < 0)
284 {
285 Log(LOG_LEVEL_ERR,
286 "Connection was hung up during identification! (3)");
287 return -1;
288 }
289 else if ((size_t) ret < OK_len || strncmp(line, OK, OK_len) != 0)
290 {
291 Log(LOG_LEVEL_ERR,
292 "Peer did not accept our identity! Responded: %s",
293 line);
294 return 0;
295 }
296
297 /* Before it contained the protocol version we requested from the server,
298 * now we put in the value that was negotiated. */
299 conn_info->protocol = wanted_version;
300
301 return 1;
302 }
303
304 /**
305 * We directly initiate a TLS handshake with the server. If the server is old
306 * version (does not speak TLS) the connection will be denied.
307 * @note the socket file descriptor in #conn_info must be connected and *not*
308 * non-blocking
309 * @return -1 in case of error
310 */
TLSTry(ConnectionInfo * conn_info)311 int TLSTry(ConnectionInfo *conn_info)
312 {
313 assert(conn_info != NULL);
314
315 if (PRIVKEY == NULL || PUBKEY == NULL)
316 {
317 Log(LOG_LEVEL_ERR, "No public/private key pair is loaded,"
318 " please create one using cf-key");
319 return -1;
320 }
321 assert(SSLCLIENTCONTEXT != NULL);
322
323 conn_info->ssl = SSL_new(SSLCLIENTCONTEXT);
324 if (conn_info->ssl == NULL)
325 {
326 Log(LOG_LEVEL_ERR, "SSL_new: %s",
327 TLSErrorString(ERR_get_error()));
328 return -1;
329 }
330
331 /* Pass conn_info inside the ssl struct for TLSVerifyCallback(). */
332 SSL_set_ex_data(conn_info->ssl, CONNECTIONINFO_SSL_IDX, conn_info);
333
334 /* Initiate the TLS handshake over the already open TCP socket. */
335 SSL_set_fd(conn_info->ssl, conn_info->sd);
336
337 bool connected = false;
338 bool should_retry = true;
339 int remaining_tries = MAX_CONNECT_RETRIES;
340 int ret;
341 while (!connected && should_retry)
342 {
343 ret = SSL_connect(conn_info->ssl);
344 /* 1 means connected, 0 means shut down, <0 means error
345 * (see man:SSL_connect(3)) */
346 connected = (ret == 1);
347 if (ret == 0)
348 {
349 should_retry = false;
350 }
351 else if (ret < 0)
352 {
353 int code = TLSLogError(conn_info->ssl, LOG_LEVEL_VERBOSE,
354 "Attempt to establish TLS connection failed", ret);
355 /* see man:SSL_connect(3) */
356 should_retry = ((remaining_tries > 0) &&
357 ((code == SSL_ERROR_WANT_READ) || (code == SSL_ERROR_WANT_WRITE)));
358 }
359 if (!connected && should_retry)
360 {
361 sleep(1);
362 remaining_tries--;
363 }
364 }
365 if (!connected)
366 {
367 TLSLogError(conn_info->ssl, LOG_LEVEL_ERR,
368 "Failed to establish TLS connection", ret);
369 return -1;
370 }
371
372 Log(LOG_LEVEL_VERBOSE, "TLS version negotiated: %8s; Cipher: %s,%s",
373 SSL_get_version(conn_info->ssl),
374 SSL_get_cipher_name(conn_info->ssl),
375 SSL_get_cipher_version(conn_info->ssl));
376 Log(LOG_LEVEL_VERBOSE, "TLS session established, checking trust...");
377
378 return 0;
379 }
380