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