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