1 /*	$NetBSD: authfile.c,v 1.28 2023/07/26 17:58:15 christos Exp $	*/
2 /* $OpenBSD: authfile.c,v 1.144 2023/03/14 07:26:25 dtucker Exp $ */
3 /*
4  * Copyright (c) 2000, 2013 Markus Friedl.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "includes.h"
28 __RCSID("$NetBSD: authfile.c,v 1.28 2023/07/26 17:58:15 christos Exp $");
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/uio.h>
32 
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <limits.h>
40 
41 #include "cipher.h"
42 #include "ssh.h"
43 #include "log.h"
44 #include "authfile.h"
45 #include "misc.h"
46 #include "atomicio.h"
47 #include "sshkey.h"
48 #include "sshbuf.h"
49 #include "ssherr.h"
50 #include "krl.h"
51 
52 #define MAX_KEY_FILE_SIZE	(1024 * 1024)
53 
54 /* Save a key blob to a file */
55 static int
sshkey_save_private_blob(struct sshbuf * keybuf,const char * filename)56 sshkey_save_private_blob(struct sshbuf *keybuf, const char *filename)
57 {
58 	int r;
59 	mode_t omask;
60 
61 	omask = umask(077);
62 	r = sshbuf_write_file(filename, keybuf);
63 	umask(omask);
64 	return r;
65 }
66 
67 int
sshkey_save_private(struct sshkey * key,const char * filename,const char * passphrase,const char * comment,int format,const char * openssh_format_cipher,int openssh_format_rounds)68 sshkey_save_private(struct sshkey *key, const char *filename,
69     const char *passphrase, const char *comment,
70     int format, const char *openssh_format_cipher, int openssh_format_rounds)
71 {
72 	struct sshbuf *keyblob = NULL;
73 	int r;
74 
75 	if ((keyblob = sshbuf_new()) == NULL)
76 		return SSH_ERR_ALLOC_FAIL;
77 	if ((r = sshkey_private_to_fileblob(key, keyblob, passphrase, comment,
78 	    format, openssh_format_cipher, openssh_format_rounds)) != 0)
79 		goto out;
80 	if ((r = sshkey_save_private_blob(keyblob, filename)) != 0)
81 		goto out;
82 	r = 0;
83  out:
84 	sshbuf_free(keyblob);
85 	return r;
86 }
87 
88 /* XXX remove error() calls from here? */
89 int
sshkey_perm_ok(int fd,const char * filename)90 sshkey_perm_ok(int fd, const char *filename)
91 {
92 	struct stat st;
93 
94 	if (fstat(fd, &st) == -1)
95 		return SSH_ERR_SYSTEM_ERROR;
96 	/*
97 	 * if a key owned by the user is accessed, then we check the
98 	 * permissions of the file. if the key owned by a different user,
99 	 * then we don't care.
100 	 */
101 	if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0) {
102 		error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
103 		error("@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @");
104 		error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
105 		error("Permissions 0%3.3o for '%s' are too open.",
106 		    (u_int)st.st_mode & 0777, filename);
107 		error("It is required that your private key files are NOT accessible by others.");
108 		error("This private key will be ignored.");
109 		return SSH_ERR_KEY_BAD_PERMISSIONS;
110 	}
111 	return 0;
112 }
113 
114 int
sshkey_load_private_type(int type,const char * filename,const char * passphrase,struct sshkey ** keyp,char ** commentp)115 sshkey_load_private_type(int type, const char *filename, const char *passphrase,
116     struct sshkey **keyp, char **commentp)
117 {
118 	int fd, r;
119 
120 	if (keyp != NULL)
121 		*keyp = NULL;
122 	if (commentp != NULL)
123 		*commentp = NULL;
124 
125 	if ((fd = open(filename, O_RDONLY)) == -1)
126 		return SSH_ERR_SYSTEM_ERROR;
127 
128 	r = sshkey_perm_ok(fd, filename);
129 	if (r != 0)
130 		goto out;
131 
132 	r = sshkey_load_private_type_fd(fd, type, passphrase, keyp, commentp);
133 	if (r == 0 && keyp && *keyp)
134 		r = sshkey_set_filename(*keyp, filename);
135  out:
136 	close(fd);
137 	return r;
138 }
139 
140 int
sshkey_load_private(const char * filename,const char * passphrase,struct sshkey ** keyp,char ** commentp)141 sshkey_load_private(const char *filename, const char *passphrase,
142     struct sshkey **keyp, char **commentp)
143 {
144 	return sshkey_load_private_type(KEY_UNSPEC, filename, passphrase,
145 	    keyp, commentp);
146 }
147 
148 int
sshkey_load_private_type_fd(int fd,int type,const char * passphrase,struct sshkey ** keyp,char ** commentp)149 sshkey_load_private_type_fd(int fd, int type, const char *passphrase,
150     struct sshkey **keyp, char **commentp)
151 {
152 	struct sshbuf *buffer = NULL;
153 	int r;
154 
155 	if (keyp != NULL)
156 		*keyp = NULL;
157 	if ((r = sshbuf_load_fd(fd, &buffer)) != 0 ||
158 	    (r = sshkey_parse_private_fileblob_type(buffer, type,
159 	    passphrase, keyp, commentp)) != 0)
160 		goto out;
161 
162 	/* success */
163 	r = 0;
164  out:
165 	sshbuf_free(buffer);
166 	return r;
167 }
168 
169 /* Load a pubkey from the unencrypted envelope of a new-format private key */
170 static int
sshkey_load_pubkey_from_private(const char * filename,struct sshkey ** pubkeyp)171 sshkey_load_pubkey_from_private(const char *filename, struct sshkey **pubkeyp)
172 {
173 	struct sshbuf *buffer = NULL;
174 	struct sshkey *pubkey = NULL;
175 	int r, fd;
176 
177 	if (pubkeyp != NULL)
178 		*pubkeyp = NULL;
179 
180 	if ((fd = open(filename, O_RDONLY)) == -1)
181 		return SSH_ERR_SYSTEM_ERROR;
182 	if ((r = sshbuf_load_fd(fd, &buffer)) != 0 ||
183 	    (r = sshkey_parse_pubkey_from_private_fileblob_type(buffer,
184 	    KEY_UNSPEC, &pubkey)) != 0)
185 		goto out;
186 	if ((r = sshkey_set_filename(pubkey, filename)) != 0)
187 		goto out;
188 	/* success */
189 	if (pubkeyp != NULL) {
190 		*pubkeyp = pubkey;
191 		pubkey = NULL;
192 	}
193 	r = 0;
194  out:
195 	close(fd);
196 	sshbuf_free(buffer);
197 	sshkey_free(pubkey);
198 	return r;
199 }
200 
201 static int
sshkey_try_load_public(struct sshkey ** kp,const char * filename,char ** commentp)202 sshkey_try_load_public(struct sshkey **kp, const char *filename,
203     char **commentp)
204 {
205 	FILE *f;
206 	char *line = NULL, *cp;
207 	size_t linesize = 0;
208 	int r;
209 	struct sshkey *k = NULL;
210 
211 	if (kp == NULL)
212 		return SSH_ERR_INVALID_ARGUMENT;
213 	*kp = NULL;
214 	if (commentp != NULL)
215 		*commentp = NULL;
216 	if ((f = fopen(filename, "r")) == NULL)
217 		return SSH_ERR_SYSTEM_ERROR;
218 	if ((k = sshkey_new(KEY_UNSPEC)) == NULL) {
219 		fclose(f);
220 		return SSH_ERR_ALLOC_FAIL;
221 	}
222 	while (getline(&line, &linesize, f) != -1) {
223 		cp = line;
224 		switch (*cp) {
225 		case '#':
226 		case '\n':
227 		case '\0':
228 			continue;
229 		}
230 		/* Abort loading if this looks like a private key */
231 		if (strncmp(cp, "-----BEGIN", 10) == 0 ||
232 		    strcmp(cp, "SSH PRIVATE KEY FILE") == 0)
233 			break;
234 		/* Skip leading whitespace. */
235 		for (; *cp && (*cp == ' ' || *cp == '\t'); cp++)
236 			;
237 		if (*cp) {
238 			if ((r = sshkey_read(k, &cp)) == 0) {
239 				cp[strcspn(cp, "\r\n")] = '\0';
240 				if (commentp) {
241 					*commentp = strdup(*cp ?
242 					    cp : filename);
243 					if (*commentp == NULL)
244 						r = SSH_ERR_ALLOC_FAIL;
245 				}
246 				/* success */
247 				*kp = k;
248 				free(line);
249 				fclose(f);
250 				return r;
251 			}
252 		}
253 	}
254 	free(k);
255 	free(line);
256 	fclose(f);
257 	return SSH_ERR_INVALID_FORMAT;
258 }
259 
260 /* load public key from any pubkey file */
261 int
sshkey_load_public(const char * filename,struct sshkey ** keyp,char ** commentp)262 sshkey_load_public(const char *filename, struct sshkey **keyp, char **commentp)
263 {
264 	char *pubfile = NULL;
265 	int r, oerrno;
266 
267 	if (keyp != NULL)
268 		*keyp = NULL;
269 	if (commentp != NULL)
270 		*commentp = NULL;
271 
272 	if ((r = sshkey_try_load_public(keyp, filename, commentp)) == 0)
273 		goto out;
274 
275 	/* try .pub suffix */
276 	if (asprintf(&pubfile, "%s.pub", filename) == -1)
277 		return SSH_ERR_ALLOC_FAIL;
278 	if ((r = sshkey_try_load_public(keyp, pubfile, commentp)) == 0)
279 		goto out;
280 
281 	/* finally, try to extract public key from private key file */
282 	if ((r = sshkey_load_pubkey_from_private(filename, keyp)) == 0)
283 		goto out;
284 
285 	/* Pretend we couldn't find the key */
286 	r = SSH_ERR_SYSTEM_ERROR;
287 	errno = ENOENT;
288 
289  out:
290 	oerrno = errno;
291 	free(pubfile);
292 	errno = oerrno;
293 	return r;
294 }
295 
296 /* Load the certificate associated with the named private key */
297 int
sshkey_load_cert(const char * filename,struct sshkey ** keyp)298 sshkey_load_cert(const char *filename, struct sshkey **keyp)
299 {
300 	struct sshkey *pub = NULL;
301 	char *file = NULL;
302 	int r = SSH_ERR_INTERNAL_ERROR;
303 
304 	if (keyp != NULL)
305 		*keyp = NULL;
306 
307 	if (asprintf(&file, "%s-cert.pub", filename) == -1)
308 		return SSH_ERR_ALLOC_FAIL;
309 
310 	r = sshkey_try_load_public(keyp, file, NULL);
311 	free(file);
312 	sshkey_free(pub);
313 	return r;
314 }
315 
316 /* Load private key and certificate */
317 int
sshkey_load_private_cert(int type,const char * filename,const char * passphrase,struct sshkey ** keyp)318 sshkey_load_private_cert(int type, const char *filename, const char *passphrase,
319     struct sshkey **keyp)
320 {
321 	struct sshkey *key = NULL, *cert = NULL;
322 	int r;
323 
324 	if (keyp != NULL)
325 		*keyp = NULL;
326 
327 	switch (type) {
328 #ifdef WITH_OPENSSL
329 	case KEY_RSA:
330 	case KEY_DSA:
331 	case KEY_ECDSA:
332 #endif /* WITH_OPENSSL */
333 	case KEY_ED25519:
334 	case KEY_XMSS:
335 	case KEY_UNSPEC:
336 		break;
337 	default:
338 		return SSH_ERR_KEY_TYPE_UNKNOWN;
339 	}
340 
341 	if ((r = sshkey_load_private_type(type, filename,
342 	    passphrase, &key, NULL)) != 0 ||
343 	    (r = sshkey_load_cert(filename, &cert)) != 0)
344 		goto out;
345 
346 	/* Make sure the private key matches the certificate */
347 	if (sshkey_equal_public(key, cert) == 0) {
348 		r = SSH_ERR_KEY_CERT_MISMATCH;
349 		goto out;
350 	}
351 
352 	if ((r = sshkey_to_certified(key)) != 0 ||
353 	    (r = sshkey_cert_copy(cert, key)) != 0)
354 		goto out;
355 	r = 0;
356 	if (keyp != NULL) {
357 		*keyp = key;
358 		key = NULL;
359 	}
360  out:
361 	sshkey_free(key);
362 	sshkey_free(cert);
363 	return r;
364 }
365 
366 /*
367  * Returns success if the specified "key" is listed in the file "filename",
368  * SSH_ERR_KEY_NOT_FOUND: if the key is not listed or another error.
369  * If "strict_type" is set then the key type must match exactly,
370  * otherwise a comparison that ignores certificate data is performed.
371  * If "check_ca" is set and "key" is a certificate, then its CA key is
372  * also checked and sshkey_in_file() will return success if either is found.
373  */
374 int
sshkey_in_file(struct sshkey * key,const char * filename,int strict_type,int check_ca)375 sshkey_in_file(struct sshkey *key, const char *filename, int strict_type,
376     int check_ca)
377 {
378 	FILE *f;
379 	char *line = NULL, *cp;
380 	size_t linesize = 0;
381 	int r = 0;
382 	struct sshkey *pub = NULL;
383 
384 	int (*sshkey_compare)(const struct sshkey *, const struct sshkey *) =
385 	    strict_type ?  sshkey_equal : sshkey_equal_public;
386 
387 	if ((f = fopen(filename, "r")) == NULL)
388 		return SSH_ERR_SYSTEM_ERROR;
389 
390 	while (getline(&line, &linesize, f) != -1) {
391 		sshkey_free(pub);
392 		pub = NULL;
393 		cp = line;
394 
395 		/* Skip leading whitespace. */
396 		for (; *cp && (*cp == ' ' || *cp == '\t'); cp++)
397 			;
398 
399 		/* Skip comments and empty lines */
400 		switch (*cp) {
401 		case '#':
402 		case '\n':
403 		case '\0':
404 			continue;
405 		}
406 
407 		if ((pub = sshkey_new(KEY_UNSPEC)) == NULL) {
408 			r = SSH_ERR_ALLOC_FAIL;
409 			goto out;
410 		}
411 		switch (r = sshkey_read(pub, &cp)) {
412 		case 0:
413 			break;
414 		case SSH_ERR_KEY_LENGTH:
415 			continue;
416 		default:
417 			goto out;
418 		}
419 		if (sshkey_compare(key, pub) ||
420 		    (check_ca && sshkey_is_cert(key) &&
421 		    sshkey_compare(key->cert->signature_key, pub))) {
422 			r = 0;
423 			goto out;
424 		}
425 	}
426 	r = SSH_ERR_KEY_NOT_FOUND;
427  out:
428 	free(line);
429 	sshkey_free(pub);
430 	fclose(f);
431 	return r;
432 }
433 
434 /*
435  * Checks whether the specified key is revoked, returning 0 if not,
436  * SSH_ERR_KEY_REVOKED if it is or another error code if something
437  * unexpected happened.
438  * This will check both the key and, if it is a certificate, its CA key too.
439  * "revoked_keys_file" may be a KRL or a one-per-line list of public keys.
440  */
441 int
sshkey_check_revoked(struct sshkey * key,const char * revoked_keys_file)442 sshkey_check_revoked(struct sshkey *key, const char *revoked_keys_file)
443 {
444 	int r;
445 
446 	r = ssh_krl_file_contains_key(revoked_keys_file, key);
447 	/* If this was not a KRL to begin with then continue below */
448 	if (r != SSH_ERR_KRL_BAD_MAGIC)
449 		return r;
450 
451 	/*
452 	 * If the file is not a KRL or we can't handle KRLs then attempt to
453 	 * parse the file as a flat list of keys.
454 	 */
455 	switch ((r = sshkey_in_file(key, revoked_keys_file, 0, 1))) {
456 	case 0:
457 		/* Key found => revoked */
458 		return SSH_ERR_KEY_REVOKED;
459 	case SSH_ERR_KEY_NOT_FOUND:
460 		/* Key not found => not revoked */
461 		return 0;
462 	default:
463 		/* Some other error occurred */
464 		return r;
465 	}
466 }
467 
468 /*
469  * Advanced *cpp past the end of key options, defined as the first unquoted
470  * whitespace character. Returns 0 on success or -1 on failure (e.g.
471  * unterminated quotes).
472  */
473 int
sshkey_advance_past_options(char ** cpp)474 sshkey_advance_past_options(char **cpp)
475 {
476 	char *cp = *cpp;
477 	int quoted = 0;
478 
479 	for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
480 		if (*cp == '\\' && cp[1] == '"')
481 			cp++;	/* Skip both */
482 		else if (*cp == '"')
483 			quoted = !quoted;
484 	}
485 	*cpp = cp;
486 	/* return failure for unterminated quotes */
487 	return (*cp == '\0' && quoted) ? -1 : 0;
488 }
489 
490 /* Save a public key */
491 int
sshkey_save_public(const struct sshkey * key,const char * path,const char * comment)492 sshkey_save_public(const struct sshkey *key, const char *path,
493     const char *comment)
494 {
495 	int fd, oerrno;
496 	FILE *f = NULL;
497 	int r = SSH_ERR_INTERNAL_ERROR;
498 
499 	if ((fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1)
500 		return SSH_ERR_SYSTEM_ERROR;
501 	if ((f = fdopen(fd, "w")) == NULL) {
502 		r = SSH_ERR_SYSTEM_ERROR;
503 		close(fd);
504 		goto fail;
505 	}
506 	if ((r = sshkey_write(key, f)) != 0)
507 		goto fail;
508 	fprintf(f, " %s\n", comment);
509 	if (ferror(f)) {
510 		r = SSH_ERR_SYSTEM_ERROR;
511 		goto fail;
512 	}
513 	if (fclose(f) != 0) {
514 		r = SSH_ERR_SYSTEM_ERROR;
515 		f = NULL;
516  fail:
517 		if (f != NULL) {
518 			oerrno = errno;
519 			fclose(f);
520 			errno = oerrno;
521 		}
522 		return r;
523 	}
524 	return 0;
525 }
526