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