cfengine  3.15.4
About: CFEngine is a configuration management system for configuring and maintaining Unix-like computers (using an own high level policy language). Community version.
  Fossies Dox: cfengine-3.15.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

tls_client.c
Go to the documentation of this file.
1 /*
2  Copyright 2019 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 
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  */
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. */
114  if (SSLCLIENTCERT == NULL)
115  {
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);
146  err2:
147  SSL_CTX_free(SSLCLIENTCONTEXT);
149  err1:
150  return false;
151 }
152 
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);
171  }
172 
173  if (SSLCLIENTCONTEXT != NULL)
174  {
175  SSL_CTX_free(SSLCLIENTCONTEXT);
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  */
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 >= sizeof(line) - line_len)
254  {
255  Log(LOG_LEVEL_ERR, "Sending IDENTITY truncated: %s", line);
256  return -1;
257  }
258  line_len += ret;
259  }
260 
261  /* Overwrite the terminating '\0', we don't need it anyway. */
262  line[line_len] = '\n';
263  line_len++;
264 
265  ret = TLSSend(conn_info->ssl, line, line_len);
266  if (ret == -1)
267  {
269  "Connection was hung up during identification! (2)");
270  return -1;
271  }
272 
273  /* Server might hang up here, after we sent identification! We
274  * must get the "OK WELCOME" message for everything to be OK. */
275  static const char OK[] = "OK WELCOME";
276  size_t OK_len = sizeof(OK) - 1;
277  ret = TLSRecvLines(conn_info->ssl, line, sizeof(line));
278  if (ret == -1)
279  {
281  "Connection was hung up during identification! (3)");
282  return -1;
283  }
284 
285  if (ret < OK_len || strncmp(line, OK, OK_len) != 0)
286  {
288  "Peer did not accept our identity! Responded: %s",
289  line);
290  return 0;
291  }
292 
293  /* Before it contained the protocol version we requested from the server,
294  * now we put in the value that was negotiated. */
295  conn_info->protocol = wanted_version;
296 
297  return 1;
298 }
299 
300 /**
301  * We directly initiate a TLS handshake with the server. If the server is old
302  * version (does not speak TLS) the connection will be denied.
303  * @note the socket file descriptor in #conn_info must be connected and *not*
304  * non-blocking
305  * @return -1 in case of error
306  */
307 int TLSTry(ConnectionInfo *conn_info)
308 {
309  assert(conn_info != NULL);
310 
311  if (PRIVKEY == NULL || PUBKEY == NULL)
312  {
313  Log(LOG_LEVEL_ERR, "No public/private key pair is loaded,"
314  " please create one using cf-key");
315  return -1;
316  }
317  assert(SSLCLIENTCONTEXT != NULL);
318 
319  conn_info->ssl = SSL_new(SSLCLIENTCONTEXT);
320  if (conn_info->ssl == NULL)
321  {
322  Log(LOG_LEVEL_ERR, "SSL_new: %s",
323  TLSErrorString(ERR_get_error()));
324  return -1;
325  }
326 
327  /* Pass conn_info inside the ssl struct for TLSVerifyCallback(). */
328  SSL_set_ex_data(conn_info->ssl, CONNECTIONINFO_SSL_IDX, conn_info);
329 
330  /* Initiate the TLS handshake over the already open TCP socket. */
331  SSL_set_fd(conn_info->ssl, conn_info->sd);
332 
333  bool connected = false;
334  bool should_retry = true;
335  int remaining_tries = MAX_CONNECT_RETRIES;
336  int ret;
337  while (!connected && should_retry)
338  {
339  ret = SSL_connect(conn_info->ssl);
340  /* 1 means connected, 0 means shut down, <0 means error
341  * (see man:SSL_connect(3)) */
342  connected = (ret == 1);
343  if (ret == 0)
344  {
345  should_retry = false;
346  }
347  else if (ret < 0)
348  {
349  int code = TLSLogError(conn_info->ssl, LOG_LEVEL_VERBOSE,
350  "Attempt to establish TLS connection failed", ret);
351  /* see man:SSL_connect(3) */
352  should_retry = ((remaining_tries > 0) &&
353  ((code == SSL_ERROR_WANT_READ) || (code == SSL_ERROR_WANT_WRITE)));
354  }
355  if (!connected && should_retry)
356  {
357  sleep(1);
358  remaining_tries--;
359  }
360  }
361  if (!connected)
362  {
363  TLSLogError(conn_info->ssl, LOG_LEVEL_ERR,
364  "Failed to establish TLS connection", ret);
365  return -1;
366  }
367 
368  Log(LOG_LEVEL_VERBOSE, "TLS version negotiated: %8s; Cipher: %s,%s",
369  SSL_get_version(conn_info->ssl),
370  SSL_get_cipher_name(conn_info->ssl),
371  SSL_get_cipher_version(conn_info->ssl));
372  Log(LOG_LEVEL_VERBOSE, "TLS session established, checking trust...");
373 
374  return 0;
375 }
#define NULL
Definition: getopt1.c:56
void Log(LogLevel level, const char *fmt,...)
Definition: logging.c:409
@ LOG_LEVEL_ERR
Definition: logging.h:42
@ LOG_LEVEL_VERBOSE
Definition: logging.h:46
unsigned int sleep(unsigned int seconds)
ProtocolVersion ParseProtocolVersionNetwork(const char *const s)
static bool ProtocolIsTLS(const ProtocolVersion p)
ProtocolVersion
@ CF_PROTOCOL_UNDEFINED
#define CF_PROTOCOL_LATEST
static bool ProtocolIsUndefined(const ProtocolVersion p)
static bool ProtocolIsClassic(const ProtocolVersion p)
ProtocolVersion protocol
static SSL_CTX * SSLCLIENTCONTEXT
Definition: tls_client.c:56
int TLSClientIdentificationDialog(ConnectionInfo *conn_info, const char *username)
Definition: tls_client.c:192
bool TLSClientIsInitialized()
Definition: tls_client.c:60
bool TLSClientInitialize(const char *tls_min_version, const char *ciphers)
Definition: tls_client.c:74
RSA * PRIVKEY
Definition: cf3globals.c:72
int TLSTry(ConnectionInfo *conn_info)
Definition: tls_client.c:307
#define MAX_CONNECT_RETRIES
Definition: tls_client.c:42
void TLSDeInitialize()
Definition: tls_client.c:153
RSA * PUBKEY
Definition: tls_client.c:44
static X509 * SSLCLIENTCERT
Definition: tls_client.c:57
int TLSRecvLines(SSL *ssl, char *buf, size_t buf_size)
Repeat receiving until last byte received is ' '.
Definition: tls_generic.c:824
X509 * TLSGenerateCertFromPrivKey(RSA *privkey)
Generate and return a dummy in-memory X509 certificate signed with the private key passed....
Definition: tls_generic.c:447
const char * TLSErrorString(intmax_t errcode)
Definition: tls_generic.c:87
bool TLSSetCipherList(SSL_CTX *ssl_ctx, const char *cipher_list)
Definition: tls_generic.c:987
void TLSSetDefaultOptions(SSL_CTX *ssl_ctx, const char *min_version)
Definition: tls_generic.c:869
bool TLSGenericInitialize()
Definition: tls_generic.c:93
int TLSLogError(SSL *ssl, LogLevel level, const char *prepend, int retcode)
OpenSSL is missing an SSL_reason_error_string() like ERR_reason_error_string(). Provide missing funct...
Definition: tls_generic.c:577
int TLSSend(SSL *ssl, const char *buffer, int length)
Sends the data stored on the buffer using a TLS session.
Definition: tls_generic.c:669
int CONNECTIONINFO_SSL_IDX
Definition: tls_generic.c:82