1 /*
2  * ProFTPD - mod_sftp RFC4716 keystore
3  * Copyright (c) 2008-2016 TJ Saunders
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18  *
19  * As a special exemption, TJ Saunders and other respective copyright holders
20  * give permission to link this program with OpenSSL, and distribute the
21  * resulting executable, without including the source code for OpenSSL in the
22  * source distribution.
23  */
24 
25 #include "mod_sftp.h"
26 #include "keys.h"
27 #include "keystore.h"
28 #include "crypto.h"
29 #include "rfc4716.h"
30 
31 /* File-based keystore implementation */
32 
33 struct filestore_key {
34   /* Supported headers.  We don't really care about the Comment header
35    * at the moment.
36    */
37   const char *subject;
38 
39   /* Key data */
40   unsigned char *key_data;
41   uint32_t key_datalen;
42 };
43 
44 struct filestore_data {
45   pr_fh_t *fh;
46   const char *path;
47   unsigned int lineno;
48 };
49 
50 static const char *trace_channel = "ssh2";
51 
52 /* This getline() function is quite similar to pr_fsio_getline(), except
53  * that it a) enforces the 72-byte max line length from RFC4716, and b)
54  * properly handles lines ending with CR, LF, or CRLF.
55  *
56  * Technically it allows one more byte than necessary, since the worst case
57  * is 74 bytes (72 + CRLF); this also means 73 + CR or 73 + LF.  The extra
58  * byte is for the terminating NUL.
59  */
filestore_getline(sftp_keystore_t * store,pool * p)60 static char *filestore_getline(sftp_keystore_t *store, pool *p) {
61   char linebuf[75], *line = "", *res;
62   struct filestore_data *store_data = store->keystore_data;
63 
64   while (TRUE) {
65     size_t linelen;
66 
67     pr_signals_handle();
68 
69     memset(&linebuf, '\0', sizeof(linebuf));
70     res = pr_fsio_gets(linebuf, sizeof(linebuf) - 1, store_data->fh);
71 
72     if (res == NULL) {
73       if (errno == EINTR) {
74         continue;
75       }
76 
77       pr_trace_msg(trace_channel, 10, "reached end of '%s', no matching "
78         "key found", store_data->path);
79       errno = EOF;
80       return NULL;
81     }
82 
83     linelen = strlen(linebuf);
84     if (linelen >= 1) {
85       if (linebuf[linelen - 1] == '\r' ||
86           linebuf[linelen - 1] == '\n') {
87         char *tmp;
88         unsigned int header_taglen, header_valuelen;
89         int have_line_continuation = FALSE;
90 
91         store_data->lineno++;
92 
93         linebuf[linelen - 1] = '\0';
94         line = pstrcat(p, line, linebuf, NULL);
95 
96         if (line[strlen(line) - 1] == '\\') {
97           have_line_continuation = TRUE;
98           line[strlen(line) - 1] = '\0';
99         }
100 
101         tmp = strchr(line, ':');
102         if (tmp == NULL) {
103           return line;
104         }
105 
106         /* We have a header.  Make sure the header tag is not longer than
107          * the specified length of 64 bytes, and that the header value is
108          * not longer than 1024 bytes.
109          */
110         header_taglen = tmp - line;
111         if (header_taglen > 64) {
112           (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
113             "header tag too long (%u) on line %u of '%s'", header_taglen,
114             store_data->lineno, store_data->path);
115           errno = EINVAL;
116           return NULL;
117         }
118 
119         /* Header value starts at 2 after the ':' (one for the mandatory
120          * space character.
121          */
122         header_valuelen = strlen(line) - (header_taglen + 2);
123         if (header_valuelen > 1024) {
124           (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
125             "header value too long (%u) on line %u of '%s'", header_valuelen,
126             store_data->lineno, store_data->path);
127           errno = EINVAL;
128           return NULL;
129         }
130 
131         if (!have_line_continuation) {
132           return line;
133         }
134 
135         continue;
136 
137       } else if (linelen >= 2 &&
138           linebuf[linelen - 2] == '\r' &&
139           linebuf[linelen - 1] == '\n') {
140         char *tmp;
141         unsigned int header_taglen, header_valuelen;
142         int have_line_continuation = FALSE;
143 
144         store_data->lineno++;
145 
146         linebuf[linelen - 2] = '\0';
147         linebuf[linelen - 1] = '\0';
148         line = pstrcat(p, line, linebuf, NULL);
149 
150         if (line[strlen(line) - 1] == '\\') {
151           have_line_continuation = TRUE;
152           line[strlen(line) - 1] = '\0';
153         }
154 
155         tmp = strchr(line, ':');
156         if (tmp == NULL) {
157           return line;
158         }
159 
160         /* We have a header.  Make sure the header tag is not longer than
161          * the specified length of 64 bytes, and that the header value is
162          * not longer than 1024 bytes.
163          */
164         header_taglen = tmp - line;
165         if (header_taglen > 64) {
166           (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
167             "header tag too long (%u) on line %u of '%s'", header_taglen,
168             store_data->lineno, store_data->path);
169           errno = EINVAL;
170           return NULL;
171         }
172 
173         /* Header value starts at 2 after the ':' (one for the mandatory
174          * space character.
175          */
176         header_valuelen = strlen(line) - (header_taglen + 2);
177         if (header_valuelen > 1024) {
178           (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
179             "header value too long (%u) on line %u of '%s'", header_valuelen,
180             store_data->lineno, store_data->path);
181           errno = EINVAL;
182           return NULL;
183         }
184 
185         if (!have_line_continuation) {
186           return line;
187         }
188 
189         continue;
190 
191       } else if (linelen < sizeof(linebuf)) {
192         /* No CR or LF terminator; maybe a badly formatted file?  Try to
193          * work with the data, if we can.
194          */
195         line = pstrcat(p, line, linebuf, NULL);
196         return line;
197 
198       } else {
199         (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
200           "line too long (%lu) on line %u of '%s'", (unsigned long) linelen,
201           store_data->lineno, store_data->path);
202         (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
203           "Make sure that '%s' is a RFC4716 formatted key", store_data->path);
204         errno = EINVAL;
205         break;
206       }
207     }
208   }
209 
210   return NULL;
211 }
212 
filestore_get_key(sftp_keystore_t * store,pool * p)213 static struct filestore_key *filestore_get_key(sftp_keystore_t *store,
214     pool *p) {
215   char *line;
216   BIO *bio = NULL;
217   struct filestore_key *key = NULL;
218   struct filestore_data *store_data = store->keystore_data;
219   size_t begin_markerlen = 0, end_markerlen = 0;
220 
221   line = filestore_getline(store, p);
222   while (line == NULL &&
223          errno == EINVAL) {
224     line = filestore_getline(store, p);
225   }
226 
227   begin_markerlen = strlen(SFTP_SSH2_PUBKEY_BEGIN_MARKER);
228   end_markerlen = strlen(SFTP_SSH2_PUBKEY_END_MARKER);
229 
230   while (line) {
231     pr_signals_handle();
232 
233     if (key == NULL &&
234         strncmp(line, SFTP_SSH2_PUBKEY_BEGIN_MARKER,
235         begin_markerlen + 1) == 0) {
236       key = pcalloc(p, sizeof(struct filestore_key));
237       bio = BIO_new(BIO_s_mem());
238 
239     } else if (key != NULL &&
240                strncmp(line, SFTP_SSH2_PUBKEY_END_MARKER,
241                  end_markerlen + 1) == 0) {
242       if (bio) {
243         BIO *b64 = NULL, *bmem = NULL;
244         char chunk[1024], *data = NULL;
245         int chunklen;
246         long datalen = 0;
247 
248         /* Add a base64 filter BIO, and read the data out, thus base64-decoding
249          * the key.  Write the decoded data into another memory BIO.
250          */
251         b64 = BIO_new(BIO_f_base64());
252         BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
253         bio = BIO_push(b64, bio);
254 
255         bmem = BIO_new(BIO_s_mem());
256 
257         memset(chunk, '\0', sizeof(chunk));
258         chunklen = BIO_read(bio, chunk, sizeof(chunk));
259 
260         if (chunklen < 0 &&
261             !BIO_should_retry(bio)) {
262           (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
263             "unable to base64-decode data in '%s': %s",
264             store_data->path, sftp_crypto_get_errors());
265           BIO_free_all(bio);
266           BIO_free_all(bmem);
267 
268           errno = EPERM;
269           return NULL;
270         }
271 
272         while (chunklen > 0) {
273           pr_signals_handle();
274 
275           if (BIO_write(bmem, chunk, chunklen) < 0) {
276             (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
277               "error writing to memory BIO: %s", sftp_crypto_get_errors());
278             BIO_free_all(bio);
279             BIO_free_all(bmem);
280 
281             errno = EPERM;
282             return NULL;
283           }
284 
285           memset(chunk, '\0', sizeof(chunk));
286           chunklen = BIO_read(bio, chunk, sizeof(chunk));
287         }
288 
289         datalen = BIO_get_mem_data(bmem, &data);
290 
291         if (data != NULL &&
292             datalen > 0) {
293           key->key_data = palloc(p, datalen);
294           key->key_datalen = datalen;
295           memcpy(key->key_data, data, datalen);
296 
297         } else {
298           (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
299             "error base64-decoding key data in '%s'", store_data->path);
300         }
301 
302         BIO_free_all(bio);
303         bio = NULL;
304 
305         BIO_free_all(bmem);
306       }
307 
308       break;
309 
310     } else {
311       if (key) {
312         if (strstr(line, ": ") != NULL) {
313           if (strncasecmp(line, "Subject: ", 9) == 0) {
314             key->subject = pstrdup(p, line + 9);
315           }
316 
317         } else {
318           if (BIO_write(bio, line, strlen(line)) < 0) {
319             (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
320               "error buffering base64 data");
321           }
322         }
323       }
324     }
325 
326     line = filestore_getline(store, p);
327     while (line == NULL &&
328            errno == EINVAL) {
329       line = filestore_getline(store, p);
330     }
331   }
332 
333   return key;
334 }
335 
filestore_verify_host_key(sftp_keystore_t * store,pool * p,const char * user,const char * host_fqdn,const char * host_user,unsigned char * key_data,uint32_t key_len)336 static int filestore_verify_host_key(sftp_keystore_t *store, pool *p,
337     const char *user, const char *host_fqdn, const char *host_user,
338     unsigned char *key_data, uint32_t key_len) {
339   struct filestore_key *key = NULL;
340   struct filestore_data *store_data = store->keystore_data;
341 
342   int res = -1;
343 
344   if (!store_data->path) {
345     errno = EPERM;
346     return -1;
347   }
348 
349   /* XXX Note that this will scan the file from the beginning, each time.
350    * There's room for improvement; perhaps mmap() the file into memory?
351    */
352 
353   key = filestore_get_key(store, p);
354   while (key) {
355     int ok;
356 
357     pr_signals_handle();
358 
359     ok = sftp_keys_compare_keys(p, key_data, key_len, key->key_data,
360       key->key_datalen);
361     if (ok != TRUE) {
362       if (ok == -1) {
363         (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
364           "error comparing keys from '%s': %s", store_data->path,
365           strerror(errno));
366       }
367 
368     } else {
369 
370       /* XXX Verify that the user and the host_user match?? */
371 
372       res = 0;
373       break;
374     }
375 
376     key = filestore_get_key(store, p);
377   }
378 
379   if (res == 0) {
380     pr_trace_msg(trace_channel, 10, "found matching public key for host '%s' "
381       "in '%s'", host_fqdn, store_data->path);
382   }
383 
384   if (pr_fsio_lseek(store_data->fh, 0, SEEK_SET) < 0) {
385     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
386       "error seeking to start of '%s': %s", store_data->path, strerror(errno));
387     return -1;
388   }
389 
390   store_data->lineno = 0;
391   return res;
392 }
393 
filestore_verify_user_key(sftp_keystore_t * store,pool * p,const char * user,unsigned char * key_data,uint32_t key_len)394 static int filestore_verify_user_key(sftp_keystore_t *store, pool *p,
395     const char *user, unsigned char *key_data, uint32_t key_len) {
396   struct filestore_key *key = NULL;
397   struct filestore_data *store_data = store->keystore_data;
398   unsigned int count = 0;
399 
400   int res = -1;
401 
402   if (!store_data->path) {
403     errno = EPERM;
404     return -1;
405   }
406 
407   /* XXX Note that this will scan the file from the beginning, each time.
408    * There's room for improvement; perhaps mmap() the file into memory?
409    */
410 
411   key = filestore_get_key(store, p);
412   while (key) {
413     int ok;
414 
415     pr_signals_handle();
416     count++;
417 
418     ok = sftp_keys_compare_keys(p, key_data, key_len, key->key_data,
419       key->key_datalen);
420     if (ok != TRUE) {
421       if (ok == -1) {
422         (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
423           "error comparing keys from '%s': %s", store_data->path,
424           strerror(errno));
425 
426       } else {
427         pr_trace_msg(trace_channel, 10,
428           "failed to match key #%u from file '%s'", count, store_data->path);
429       }
430 
431     } else {
432       /* If we are configured to check for Subject headers, and if the file key
433        * has a Subject header, and that header value does not match the
434        * logging in user, then continue looking.
435        */
436       if ((sftp_opts & SFTP_OPT_MATCH_KEY_SUBJECT) &&
437           key->subject != NULL) {
438         if (strcmp(key->subject, user) != 0) {
439           (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
440             "found matching key for user '%s' in '%s', but Subject "
441             "header ('%s') does not match, skipping key", user,
442             store_data->path, key->subject);
443 
444         } else {
445           res = 0;
446           break;
447         }
448 
449       } else {
450         res = 0;
451         break;
452       }
453     }
454 
455     key = filestore_get_key(store, p);
456   }
457 
458   if (res == 0) {
459     pr_trace_msg(trace_channel, 10, "found matching public key for user '%s' "
460       "in '%s'", user, store_data->path);
461   }
462 
463   if (pr_fsio_lseek(store_data->fh, 0, SEEK_SET) < 0) {
464     (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
465       "error seeking to start of '%s': %s", store_data->path, strerror(errno));
466     return -1;
467   }
468 
469   store_data->lineno = 0;
470   return res;
471 }
472 
filestore_close(sftp_keystore_t * store)473 static int filestore_close(sftp_keystore_t *store) {
474   struct filestore_data *store_data = store->keystore_data;
475 
476   pr_fsio_close(store_data->fh);
477   return 0;
478 }
479 
filestore_open(pool * parent_pool,int requested_key_type,const char * store_info,const char * user)480 static sftp_keystore_t *filestore_open(pool *parent_pool,
481     int requested_key_type, const char *store_info, const char *user) {
482   int xerrno;
483   sftp_keystore_t *store;
484   pool *filestore_pool;
485   struct filestore_data *store_data;
486   pr_fh_t *fh;
487   char buf[PR_TUNABLE_PATH_MAX+1], *path;
488   struct stat st;
489 
490   filestore_pool = make_sub_pool(parent_pool);
491   pr_pool_tag(filestore_pool, "SFTP File-based Keystore Pool");
492 
493   store = pcalloc(filestore_pool, sizeof(sftp_keystore_t));
494   store->keystore_pool = filestore_pool;
495 
496   /* Open the file.  The given path (store_info) may need to be
497    * interpolated.
498    */
499   session.user = (char *) user;
500 
501   memset(buf, '\0', sizeof(buf));
502   switch (pr_fs_interpolate(store_info, buf, sizeof(buf)-1)) {
503     case 1:
504       /* Interpolate occurred; make a copy of the interpolated path. */
505       path = pstrdup(filestore_pool, buf);
506       break;
507 
508     default:
509       /* Otherwise, use the path as is. */
510       path = pstrdup(filestore_pool, store_info);
511       break;
512   }
513 
514   session.user = NULL;
515 
516   PRIVS_ROOT
517   fh = pr_fsio_open(path, O_RDONLY|O_NONBLOCK);
518   xerrno = errno;
519   PRIVS_RELINQUISH
520 
521   if (fh == NULL) {
522     destroy_pool(filestore_pool);
523     errno = xerrno;
524     return NULL;
525   }
526 
527   if (pr_fsio_set_block(fh) < 0) {
528    xerrno = errno;
529 
530     destroy_pool(filestore_pool);
531     (void) pr_fsio_close(fh);
532 
533     errno = xerrno;
534     return NULL;
535   }
536 
537   /* Stat the opened file to determine the optimal buffer size for IO. */
538   memset(&st, 0, sizeof(st));
539   if (pr_fsio_fstat(fh, &st) < 0) {
540     xerrno = errno;
541 
542     destroy_pool(filestore_pool);
543     (void) pr_fsio_close(fh);
544 
545     errno = xerrno;
546     return NULL;
547   }
548 
549   if (S_ISDIR(st.st_mode)) {
550     destroy_pool(filestore_pool);
551     (void) pr_fsio_close(fh);
552 
553     errno = EISDIR;
554     return NULL;
555   }
556 
557   fh->fh_iosz = st.st_blksize;
558 
559   store_data = pcalloc(filestore_pool, sizeof(struct filestore_data));
560   store->keystore_data = store_data;
561 
562   store_data->path = path;
563   store_data->fh = fh;
564   store_data->lineno = 0;
565 
566   store->store_ktypes = requested_key_type;
567 
568   switch (requested_key_type) {
569     case SFTP_SSH2_HOST_KEY_STORE:
570       store->verify_host_key = filestore_verify_host_key;
571       break;
572 
573     case SFTP_SSH2_USER_KEY_STORE:
574       store->verify_user_key = filestore_verify_user_key;
575       break;
576   }
577 
578   store->store_close = filestore_close;
579   return store;
580 }
581 
sftp_rfc4716_init(void)582 int sftp_rfc4716_init(void) {
583   sftp_keystore_register_store("file", filestore_open,
584     SFTP_SSH2_HOST_KEY_STORE|SFTP_SSH2_USER_KEY_STORE);
585 
586   return 0;
587 }
588 
sftp_rfc4716_free(void)589 int sftp_rfc4716_free(void) {
590   sftp_keystore_unregister_store("file",
591     SFTP_SSH2_HOST_KEY_STORE|SFTP_SSH2_USER_KEY_STORE);
592 
593   return 0;
594 }
595