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