Coverage Report

Created: 2024-06-18 06:24

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