1 /*
2 Copyright 2020 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 #include <platform.h>
26 #include <cf-key-functions.h>
27
28 #include <openssl/bn.h> /* BN_*, BIGNUM */
29 #include <openssl/rand.h> /* RAND_* */
30 #include <libcrypto-compat.h>
31
32 #include <lastseen.h>
33 #include <dir.h>
34 #include <scope.h>
35 #include <files_copy.h>
36 #include <files_interfaces.h>
37 #include <hash.h>
38 #include <keyring.h>
39 #include <communication.h>
40 #include <eval_context.h>
41 #include <crypto.h>
42 #include <file_lib.h>
43 #include <known_dirs.h>
44
45
46 /*****************************************************************************/
47
48 /** Print digest of the specified public key file.
49 Return 0 on success and 1 on error. */
PrintDigest(const char * pubkey)50 int PrintDigest(const char *pubkey)
51 {
52 char *digeststr = LoadPubkeyDigest(pubkey);
53
54 if (digeststr == NULL)
55 {
56 return 1; /* ERROR exitcode */
57 }
58
59 fprintf(stdout, "%s\n", digeststr);
60 free(digeststr);
61 return 0; /* OK exitcode */
62 }
63
64 /**
65 * Split a "key" argument of the form "[[user@]address:]filename" into
66 * components (public) key file name, IP address, and (remote) user
67 * name. Pointers to the corresponding segments of the #keyarg
68 * string will be written into the three output arguments #filename,
69 * #ipaddr, and #username. (Hence, the three output string have
70 * the same lifetime/scope as the #keyarg string.)
71 *
72 * The only required component is the file name. If IP address is
73 * missing, NULL is written into the #ipaddr pointer. If the
74 * username is missing, #username will point to the constant string
75 * "root".
76 *
77 * @NOTE the #keyarg argument is modified by this function!
78 */
ParseKeyArg(char * keyarg,char ** filename,char ** ipaddr,char ** username)79 void ParseKeyArg(char *keyarg, char **filename, char **ipaddr, char **username)
80 {
81 char *s;
82
83 /* set defaults */
84 *ipaddr = NULL;
85 *username = "root";
86
87 /* use rightmost colon so we can cope with IPv6 addresses */
88 s = strrchr(keyarg, ':');
89 if (s == NULL)
90 {
91 /* no colon, entire argument is a filename */
92 *filename = keyarg;
93 return;
94 }
95
96 *s = '\0'; /* split string */
97 *filename = s + 1; /* filename starts at 1st character after ':' */
98
99 s = strchr(keyarg, '@');
100 if (s == NULL)
101 {
102 /* no username given, use default */
103 *ipaddr = keyarg;
104 return;
105 }
106
107 *s = '\0';
108 *ipaddr = s + 1;
109 *username = keyarg;
110
111 /* special case: if we got user@:/path/to/file
112 then reset ipaddr to NULL instead of empty string */
113 if (**ipaddr == '\0')
114 {
115 *ipaddr = NULL;
116 }
117
118 return;
119 }
120
121 extern bool cf_key_interrupted;
122
123 #define HOST_FMT_TRUNCATE "%-10.10s %-40.40s %-25.25s %-26.26s %-s\n"
124 #define HOST_FMT_NO_TRUNCATE "%s\t%s\t%s\t%s\t%s\n"
125
126 typedef struct _HostPrintState
127 {
128 int count;
129 bool truncate;
130 } HostPrintState;
131
ShowHost(const char * const hostkey,const char * const address,bool incoming,const KeyHostSeen * const quality,void * const ctx)132 static bool ShowHost(
133 const char *const hostkey,
134 const char *const address,
135 bool incoming,
136 const KeyHostSeen *const quality,
137 void *const ctx)
138 {
139 HostPrintState *const state = ctx;
140 char hostname[NI_MAXHOST];
141 if (LOOKUP_HOSTS)
142 {
143 int ret = IPString2Hostname(hostname, address, sizeof(hostname));
144 if (ret == -1)
145 {
146 strcpy(hostname, "-");
147 }
148 }
149 else
150 {
151 strlcpy(hostname, address, sizeof(hostname));
152 }
153 ++(state->count);
154
155 bool truncate = state->truncate;
156 char timebuf[26];
157 printf(truncate ? HOST_FMT_TRUNCATE : HOST_FMT_NO_TRUNCATE,
158 incoming ? "Incoming" : "Outgoing",
159 address, hostname,
160 cf_strtimestamp_local(quality->lastseen, timebuf), hostkey);
161
162 return !cf_key_interrupted;
163 }
164
ShowLastSeenHosts(bool truncate)165 void ShowLastSeenHosts(bool truncate)
166 {
167 HostPrintState state = { 0 };
168 state.count = 0;
169 state.truncate = truncate;
170
171 printf(
172 truncate ? HOST_FMT_TRUNCATE : HOST_FMT_NO_TRUNCATE,
173 "Direction",
174 "IP",
175 "Name",
176 "Last connection",
177 "Key");
178
179 if (!ScanLastSeenQuality(ShowHost, &state))
180 {
181 Log(LOG_LEVEL_ERR, "Unable to show lastseen database");
182 return;
183 }
184
185 printf("Total Entries: %d\n", state.count);
186 }
187
188 /**
189 * @brief removes all traces of entry 'input' from lastseen and filesystem
190 *
191 * @param[in] key digest (SHA/MD5 format) or free host name string
192 * @param[in] must_be_coherent. false : delete if lastseen is incoherent,
193 * true : don't if lastseen is incoherent
194 * @retval 0 if entry was deleted, >0 otherwise
195 */
RemoveKeys(const char * input,bool must_be_coherent)196 int RemoveKeys(const char *input, bool must_be_coherent)
197 {
198 int res = 0;
199 char equivalent[CF_BUFSIZE];
200 equivalent[0] = '\0';
201
202 res = RemoveKeysFromLastSeen(input, must_be_coherent, equivalent, sizeof(equivalent));
203 if (res!=0)
204 {
205 return res;
206 }
207
208 Log(LOG_LEVEL_INFO, "Removed corresponding entries from lastseen database.");
209
210 int removed_input = RemovePublicKey(input);
211 int removed_equivalent = RemovePublicKey(equivalent);
212
213 if ((removed_input == -1) || (removed_equivalent == -1))
214 {
215 Log(LOG_LEVEL_ERR, "Last seen database: unable to remove keys for the entry '%s'", input);
216 return 255;
217 }
218 else if (removed_input + removed_equivalent == 0)
219 {
220 Log(LOG_LEVEL_ERR, "No key file(s) for entry '%s' were found on the filesystem", input);
221 return 1;
222 }
223 else
224 {
225 Log(LOG_LEVEL_INFO, "Removed %d corresponding key file(s) from filesystem.",
226 removed_input + removed_equivalent);
227 return 0;
228 }
229
230 return -1;
231 }
232
KeepKeyPromisesRSA(RSA * pair,const char * public_key_file,const char * private_key_file)233 static bool KeepKeyPromisesRSA(RSA *pair, const char *public_key_file, const char *private_key_file)
234 {
235 FILE *fp = safe_fopen_create_perms(private_key_file, "w", 0600);
236 if (fp == NULL)
237 {
238 Log(LOG_LEVEL_ERR,
239 "Error while writing private key file '%s' (fopen: %s)",
240 private_key_file, GetErrorStr());
241 return false;
242 }
243
244 Log(LOG_LEVEL_VERBOSE, "Writing private key to '%s'", private_key_file);
245
246 int res = PEM_write_RSAPrivateKey(fp, pair, NULL, NULL, 0, NULL, NULL);
247 fclose(fp);
248
249 if (res == 0)
250 {
251 Log(LOG_LEVEL_ERR,
252 "Couldn't write private key. (PEM_write_RSAPrivateKey: %s)",
253 CryptoLastErrorString());
254 return false;
255 }
256
257 fp = safe_fopen_create_perms(public_key_file, "w", 0600);
258 if (fp == NULL)
259 {
260 Log(LOG_LEVEL_ERR,
261 "Error while writing public key file '%s' (fopen: %s)",
262 public_key_file, GetErrorStr());
263 return false;
264 }
265
266 Log(LOG_LEVEL_VERBOSE, "Writing public key to file '%s'", public_key_file);
267
268 if (!PEM_write_RSAPublicKey(fp, pair))
269 {
270 Log(LOG_LEVEL_ERR,
271 "Unable to write public key. (PEM_write_RSAPublicKey: %s)",
272 CryptoLastErrorString());
273 return false;
274 }
275
276 fclose(fp);
277
278 char vbuff[CF_BUFSIZE];
279 snprintf(vbuff, CF_BUFSIZE, "%s%crandseed", GetWorkDir(), FILE_SEPARATOR);
280 Log(LOG_LEVEL_VERBOSE, "Using '%s' for randseed", vbuff);
281
282 if (RAND_write_file(vbuff) != 1024)
283 {
284 Log(LOG_LEVEL_ERR, "Unable to write randseed");
285 unlink(vbuff); /* randseed isn't safe to use */
286 return false;
287 }
288
289 if (chmod(vbuff, 0600) != 0)
290 {
291 Log(LOG_LEVEL_ERR,
292 "Unable to set permissions on '%s' (chmod: %s)",
293 vbuff, GetErrorStr());
294 return false;
295 }
296
297 return true;
298 }
299
KeepKeyPromises(const char * public_key_file,const char * private_key_file,const int key_size)300 bool KeepKeyPromises(const char *public_key_file, const char *private_key_file, const int key_size)
301 {
302 struct stat statbuf;
303
304 if (stat(public_key_file, &statbuf) != -1)
305 {
306 Log(LOG_LEVEL_ERR, "A key file already exists at %s", public_key_file);
307 return false;
308 }
309
310 if (stat(private_key_file, &statbuf) != -1)
311 {
312 Log(LOG_LEVEL_ERR, "A key file already exists at %s", private_key_file);
313 return false;
314 }
315
316 Log(LOG_LEVEL_INFO, "Making a key pair for CFEngine, please wait, this could take a minute...");
317
318 #ifdef OPENSSL_NO_DEPRECATED
319 RSA *pair = RSA_new();
320 BIGNUM *rsa_bignum = BN_new();
321 if (pair != NULL && rsa_bignum != NULL)
322 {
323 BN_set_word(rsa_bignum, RSA_F4);
324 int res = RSA_generate_key_ex(pair, key_size, rsa_bignum, NULL);
325 if (res == 0)
326 {
327 DESTROY_AND_NULL(RSA_free, pair); // pair = NULL
328 }
329 }
330 else
331 {
332 DESTROY_AND_NULL(RSA_free, pair); // pair = NULL
333 }
334
335 BN_clear_free(rsa_bignum);
336
337 #else
338 RSA *pair = RSA_generate_key(key_size, 65537, NULL, NULL);
339
340 #endif
341 if (pair == NULL)
342 {
343 Log(LOG_LEVEL_ERR, "Unable to generate cryptographic key (RSA_generate_key: %s)",
344 CryptoLastErrorString());
345 return false;
346 }
347 bool ret = KeepKeyPromisesRSA(pair, public_key_file, private_key_file);
348 RSA_free(pair);
349 return ret;
350 }
351
352
ENTERPRISE_FUNC_1ARG_DEFINE_STUB(bool,LicenseInstall,ARG_UNUSED char *,path_source)353 ENTERPRISE_FUNC_1ARG_DEFINE_STUB(bool, LicenseInstall, ARG_UNUSED char *, path_source)
354 {
355 Log(LOG_LEVEL_ERR, "License installation only applies to CFEngine Enterprise");
356
357 return false;
358 }
359
ForceKeyRemoval(const char * hash)360 int ForceKeyRemoval(const char *hash)
361 {
362 /**
363 Removal of a key hash is made of two passes :
364 Pass #1 (read-only)
365 -> fetches the IP addresses directly linked to the hash key
366 Pass #2 (made of deletes)
367 -> remove all the IP addresses in the previous list
368 -> remove the physical key.pub from the filesystem
369
370 WARNING: Please backup your lastseen database before calling this
371 function in the case where a 1-to-1 relatioship between
372 the IP and a single keyhash does not exist
373 **/
374 CF_DB *dbp;
375 CF_DBC *dbcp;
376 char *key;
377 void *value;
378 int ksize, vsize;
379
380 Seq *hostips = SeqNew(100, free);
381 if (OpenDB(&dbp, dbid_lastseen))
382 {
383 if (NewDBCursor(dbp, &dbcp))
384 {
385 while (NextDB(dbcp, &key, &ksize, &value, &vsize))
386 {
387 if ((key[0] != 'a') || (value == NULL))
388 {
389 continue;
390 }
391 if (!strncmp(hash, value, strlen(hash)))
392 {
393 SeqAppend(hostips, xstrdup(key + 1));
394 }
395 }
396 DeleteDBCursor(dbcp);
397 }
398 CloseDB(dbp);
399 }
400 if (OpenDB(&dbp, dbid_lastseen))
401 {
402 char tmp[CF_BUFSIZE];
403 snprintf(tmp, CF_BUFSIZE, "k%s", hash);
404 char vtmp[CF_BUFSIZE];
405 if (!ReadDB(dbp, tmp, &vtmp, sizeof(vtmp)))
406 {
407 Log(LOG_LEVEL_ERR, "Failed to read the main hash key entry '%s'. Will continue to purge other entries related to it.", hash);
408 }
409 else
410 {
411 SeqAppend(hostips, xstrdup(vtmp + 1));
412 }
413 snprintf(tmp, CF_BUFSIZE, "k%s", hash);
414 DeleteDB(dbp, tmp);
415 snprintf(tmp, CF_BUFSIZE, "qi%s", hash);
416 DeleteDB(dbp, tmp);
417 snprintf(tmp, CF_BUFSIZE, "qo%s", hash);
418 DeleteDB(dbp, tmp);
419 RemovePublicKey(hash);
420 for (int i = 0; i < SeqLength(hostips); ++i)
421 {
422 const char *myip = SeqAt(hostips, i);
423 snprintf(tmp, CF_BUFSIZE, "a%s", myip);
424 DeleteDB(dbp, tmp);
425 }
426 CloseDB(dbp);
427 }
428 SeqDestroy(hostips);
429 return 0;
430 }
431
ForceIpAddressRemoval(const char * ip)432 int ForceIpAddressRemoval(const char *ip)
433 {
434 /**
435 Removal of an ip is made of two passes :
436 Pass #1 (read-only)
437 -> fetches the key hashes directly linked to the ip address
438 Pass #2 (made of deletes)
439 -> remove all the key hashes in the previous list
440 -> remove the physical key hashes .pub files from the filesystem
441
442 WARNING: Please backup your lastseen database before calling this
443 function in the case where a 1-to-1 relatioship between
444 the IP and a single keyhash does not exist
445 **/
446 CF_DB *dbp;
447 CF_DBC *dbcp;
448 char *key;
449 void *value;
450 int ksize, vsize;
451
452 Seq *hostkeys = SeqNew(100, free);
453 if (OpenDB(&dbp, dbid_lastseen))
454 {
455 if (NewDBCursor(dbp, &dbcp))
456 {
457 while (NextDB(dbcp, &key, &ksize, &value, &vsize))
458 {
459 if ((key[0] != 'k') || (value == NULL))
460 {
461 continue;
462 }
463 if (!strncmp(ip, value, strlen(ip)))
464 {
465 SeqAppend(hostkeys, xstrdup(key + 1));
466 }
467 }
468 DeleteDBCursor(dbcp);
469 }
470 CloseDB(dbp);
471 }
472 if (OpenDB(&dbp, dbid_lastseen))
473 {
474 char tmp[CF_BUFSIZE];
475 snprintf(tmp, CF_BUFSIZE, "a%s", ip);
476 char vtmp[CF_BUFSIZE];
477 if (!ReadDB(dbp, tmp, &vtmp, sizeof(vtmp)))
478 {
479 Log(LOG_LEVEL_ERR, "Failed to read the main ip address entry '%s'. Will continue to purge other entries related to it.", ip);
480 }
481 else
482 {
483 SeqAppend(hostkeys, xstrdup(vtmp + 1));
484 }
485 snprintf(tmp, CF_BUFSIZE, "a%s", ip);
486 DeleteDB(dbp, tmp);
487 for (int i = 0; i < SeqLength(hostkeys); ++i)
488 {
489 const char *myk = SeqAt(hostkeys, i);
490 snprintf(tmp, CF_BUFSIZE, "k%s", myk);
491 DeleteDB(dbp, tmp);
492 snprintf(tmp, CF_BUFSIZE, "qi%s", myk);
493 DeleteDB(dbp, tmp);
494 snprintf(tmp, CF_BUFSIZE, "qo%s", myk);
495 DeleteDB(dbp, tmp);
496 RemovePublicKey(myk);
497 }
498 CloseDB(dbp);
499 }
500 SeqDestroy(hostkeys);
501 return 0;
502 }
503