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