Coverage Report

Created: 2026-10-17 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssh/dns.c
Line
Count
Source
1
/* $OpenBSD: dns.c,v 1.48 2026/03/03 09:57:25 dtucker Exp $ */
2
3
/*
4
 * Copyright (c) 2003 Wesley Griffin. All rights reserved.
5
 * Copyright (c) 2003 Jakob Schlyter. All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 * 1. Redistributions of source code must retain the above copyright
11
 *    notice, this list of conditions and the following disclaimer.
12
 * 2. Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
 */
27
28
#include "includes.h"
29
30
#include <sys/types.h>
31
#include <sys/socket.h>
32
33
#include <netdb.h>
34
#include <stdarg.h>
35
#include <stdio.h>
36
#include <string.h>
37
#include <stdlib.h>
38
39
#include "xmalloc.h"
40
#include "sshkey.h"
41
#include "dns.h"
42
#include "log.h"
43
#include "digest.h"
44
45
static const char * const errset_text[] = {
46
  "success",    /* 0 ERRSET_SUCCESS */
47
  "out of memory",  /* 1 ERRSET_NOMEMORY */
48
  "general failure",  /* 2 ERRSET_FAIL */
49
  "invalid parameter",  /* 3 ERRSET_INVAL */
50
  "name does not exist",  /* 4 ERRSET_NONAME */
51
  "data does not exist",  /* 5 ERRSET_NODATA */
52
};
53
54
static const char *
55
dns_result_totext(unsigned int res)
56
0
{
57
0
  switch (res) {
58
0
  case ERRSET_SUCCESS:
59
0
    return errset_text[ERRSET_SUCCESS];
60
0
  case ERRSET_NOMEMORY:
61
0
    return errset_text[ERRSET_NOMEMORY];
62
0
  case ERRSET_FAIL:
63
0
    return errset_text[ERRSET_FAIL];
64
0
  case ERRSET_INVAL:
65
0
    return errset_text[ERRSET_INVAL];
66
0
  case ERRSET_NONAME:
67
0
    return errset_text[ERRSET_NONAME];
68
0
  case ERRSET_NODATA:
69
0
    return errset_text[ERRSET_NODATA];
70
0
  default:
71
0
    return "unknown error";
72
0
  }
73
0
}
74
75
/*
76
 * Read SSHFP parameters from key buffer.
77
 * Caller must free digest which is allocated by sshkey_fingerprint_raw().
78
 */
79
static int
80
dns_read_key(uint8_t *algorithm, uint8_t *digest_type,
81
    u_char **digest, size_t *digest_len, struct sshkey *key)
82
0
{
83
0
  int r, success = 0;
84
0
  int fp_alg = -1;
85
86
0
  switch (key->type) {
87
0
  case KEY_RSA:
88
0
    *algorithm = SSHFP_KEY_RSA;
89
0
    break;
90
0
  case KEY_ECDSA:
91
0
    *algorithm = SSHFP_KEY_ECDSA;
92
0
    break;
93
0
  case KEY_ED25519:
94
0
    *algorithm = SSHFP_KEY_ED25519;
95
0
    break;
96
0
  default:
97
0
    *algorithm = SSHFP_KEY_RESERVED; /* 0 */
98
0
  }
99
100
0
  switch (*digest_type) {
101
0
  case SSHFP_HASH_SHA1:
102
0
    fp_alg = SSH_DIGEST_SHA1;
103
0
    break;
104
0
  case SSHFP_HASH_SHA256:
105
0
    fp_alg = SSH_DIGEST_SHA256;
106
0
    break;
107
0
  default:
108
0
    *digest_type = SSHFP_HASH_RESERVED; /* 0 */
109
0
  }
110
111
0
  if (*algorithm && *digest_type) {
112
0
    if ((r = sshkey_fingerprint_raw(key, fp_alg, digest,
113
0
        digest_len)) != 0)
114
0
      fatal_fr(r, "sshkey_fingerprint_raw");
115
0
    success = 1;
116
0
  } else {
117
0
    *digest = NULL;
118
0
    *digest_len = 0;
119
0
  }
120
121
0
  return success;
122
0
}
123
124
/*
125
 * Read SSHFP parameters from rdata buffer.
126
 */
127
static int
128
dns_read_rdata(uint8_t *algorithm, uint8_t *digest_type,
129
    u_char **digest, size_t *digest_len, u_char *rdata, int rdata_len)
130
0
{
131
0
  int success = 0;
132
133
0
  *algorithm = SSHFP_KEY_RESERVED;
134
0
  *digest_type = SSHFP_HASH_RESERVED;
135
136
0
  if (rdata_len >= 2) {
137
0
    *algorithm = rdata[0];
138
0
    *digest_type = rdata[1];
139
0
    *digest_len = rdata_len - 2;
140
141
0
    if (*digest_len > 0) {
142
0
      *digest = xmalloc(*digest_len);
143
0
      memcpy(*digest, rdata + 2, *digest_len);
144
0
    } else {
145
0
      *digest = (u_char *)xstrdup("");
146
0
    }
147
148
0
    success = 1;
149
0
  }
150
151
0
  return success;
152
0
}
153
154
/*
155
 * Check if hostname is numerical.
156
 * Returns -1 if hostname is numeric, 0 otherwise
157
 */
158
static int
159
is_numeric_hostname(const char *hostname)
160
0
{
161
0
  struct addrinfo hints, *ai;
162
163
  /*
164
   * We shouldn't ever get a null host but if we do then log an error
165
   * and return -1 which stops DNS key fingerprint processing.
166
   */
167
0
  if (hostname == NULL) {
168
0
    error("is_numeric_hostname called with NULL hostname");
169
0
    return -1;
170
0
  }
171
172
0
  memset(&hints, 0, sizeof(hints));
173
0
  hints.ai_socktype = SOCK_DGRAM;
174
0
  hints.ai_flags = AI_NUMERICHOST;
175
176
0
  if (getaddrinfo(hostname, NULL, &hints, &ai) == 0) {
177
0
    freeaddrinfo(ai);
178
0
    return -1;
179
0
  }
180
181
0
  return 0;
182
0
}
183
184
/*
185
 * Verify the given hostname, address and host key using DNS.
186
 * Returns 0 if lookup succeeds, -1 otherwise
187
 */
188
int
189
verify_host_key_dns(const char *hostname, struct sockaddr *address,
190
    struct sshkey *hostkey, int *flags)
191
0
{
192
0
  u_int counter;
193
0
  int result;
194
0
  struct rrsetinfo *fingerprints = NULL;
195
196
0
  uint8_t hostkey_algorithm;
197
0
  u_char *hostkey_digest;
198
0
  size_t hostkey_digest_len;
199
200
0
  uint8_t dnskey_algorithm;
201
0
  uint8_t dnskey_digest_type;
202
0
  u_char *dnskey_digest;
203
0
  size_t dnskey_digest_len;
204
205
0
  *flags = 0;
206
207
0
  debug3("verify_host_key_dns");
208
0
  if (hostkey == NULL)
209
0
    fatal("No key to look up!");
210
211
0
  if (is_numeric_hostname(hostname)) {
212
0
    debug("skipped DNS lookup for numerical hostname");
213
0
    return -1;
214
0
  }
215
216
0
  result = getrrsetbyname(hostname, DNS_RDATACLASS_IN,
217
0
      DNS_RDATATYPE_SSHFP, 0, &fingerprints);
218
0
  if (result) {
219
0
    verbose("DNS lookup error: %s", dns_result_totext(result));
220
0
    return -1;
221
0
  }
222
223
0
  if (fingerprints->rri_flags & RRSET_VALIDATED) {
224
0
    *flags |= DNS_VERIFY_SECURE;
225
0
    debug("found %d secure fingerprints in DNS",
226
0
        fingerprints->rri_nrdatas);
227
0
  } else {
228
0
    debug("found %d insecure fingerprints in DNS",
229
0
        fingerprints->rri_nrdatas);
230
0
  }
231
232
0
  if (fingerprints->rri_nrdatas)
233
0
    *flags |= DNS_VERIFY_FOUND;
234
235
0
  for (counter = 0; counter < fingerprints->rri_nrdatas; counter++) {
236
    /*
237
     * Extract the key from the answer. Ignore any badly
238
     * formatted fingerprints.
239
     */
240
0
    if (!dns_read_rdata(&dnskey_algorithm, &dnskey_digest_type,
241
0
        &dnskey_digest, &dnskey_digest_len,
242
0
        fingerprints->rri_rdatas[counter].rdi_data,
243
0
        fingerprints->rri_rdatas[counter].rdi_length)) {
244
0
      verbose("Error parsing fingerprint from DNS.");
245
0
      continue;
246
0
    }
247
0
    debug3_f("checking SSHFP type %d fptype %d", dnskey_algorithm,
248
0
        dnskey_digest_type);
249
250
    /* Calculate host key fingerprint. */
251
0
    if (!dns_read_key(&hostkey_algorithm, &dnskey_digest_type,
252
0
        &hostkey_digest, &hostkey_digest_len, hostkey)) {
253
0
      error("Error calculating key fingerprint.");
254
0
      free(dnskey_digest);
255
0
      freerrset(fingerprints);
256
0
      return -1;
257
0
    }
258
259
    /* Check if the current key is the same as the given key */
260
0
    if (hostkey_algorithm == dnskey_algorithm &&
261
0
        hostkey_digest_len == dnskey_digest_len) {
262
0
      if (timingsafe_bcmp(hostkey_digest, dnskey_digest,
263
0
          hostkey_digest_len) == 0) {
264
0
        debug_f("matched SSHFP type %d fptype %d",
265
0
            dnskey_algorithm, dnskey_digest_type);
266
0
        *flags |= DNS_VERIFY_MATCH;
267
0
      } else {
268
0
        debug_f("failed SSHFP type %d fptype %d",
269
0
            dnskey_algorithm, dnskey_digest_type);
270
0
        *flags |= DNS_VERIFY_FAILED;
271
0
      }
272
0
    }
273
0
    free(dnskey_digest);
274
0
    free(hostkey_digest); /* from sshkey_fingerprint_raw() */
275
0
  }
276
277
0
  freerrset(fingerprints);
278
279
  /* If any fingerprint failed to validate, return failure. */
280
0
  if (*flags & DNS_VERIFY_FAILED)
281
0
    *flags &= ~DNS_VERIFY_MATCH;
282
283
0
  if (*flags & DNS_VERIFY_FOUND)
284
0
    if (*flags & DNS_VERIFY_MATCH)
285
0
      debug("matching host key fingerprint found in DNS");
286
0
    else
287
0
      debug("mismatching host key fingerprint found in DNS");
288
0
  else
289
0
    debug("no host key fingerprint found in DNS");
290
291
0
  return 0;
292
0
}
293
294
/*
295
 * Export the fingerprint of a key as a DNS resource record
296
 */
297
int
298
export_dns_rr(const char *hostname, struct sshkey *key, FILE *f, int generic,
299
    int alg)
300
0
{
301
0
  uint8_t rdata_pubkey_algorithm = 0;
302
0
  uint8_t rdata_digest_type = SSHFP_HASH_RESERVED;
303
0
  uint8_t dtype;
304
0
  u_char *rdata_digest;
305
0
  size_t i, rdata_digest_len;
306
0
  int success = 0;
307
308
0
  for (dtype = SSHFP_HASH_SHA1; dtype < SSHFP_HASH_MAX; dtype++) {
309
0
    if (alg != -1 && dtype != alg)
310
0
      continue;
311
0
    rdata_digest_type = dtype;
312
0
    if (dns_read_key(&rdata_pubkey_algorithm, &rdata_digest_type,
313
0
        &rdata_digest, &rdata_digest_len, key)) {
314
0
      if (generic) {
315
0
        fprintf(f, "%s IN TYPE%d \\# %zu %02x %02x ",
316
0
            hostname, DNS_RDATATYPE_SSHFP,
317
0
            2 + rdata_digest_len,
318
0
            rdata_pubkey_algorithm, rdata_digest_type);
319
0
      } else {
320
0
        fprintf(f, "%s IN SSHFP %d %d ", hostname,
321
0
            rdata_pubkey_algorithm, rdata_digest_type);
322
0
      }
323
0
      for (i = 0; i < rdata_digest_len; i++)
324
0
        fprintf(f, "%02x", rdata_digest[i]);
325
0
      fprintf(f, "\n");
326
0
      free(rdata_digest); /* from sshkey_fingerprint_raw() */
327
0
      success = 1;
328
0
    }
329
0
  }
330
331
  /* No SSHFP record was generated at all */
332
0
  if (success == 0) {
333
0
    error_f("unsupported algorithm and/or digest_type");
334
0
  }
335
336
0
  return success;
337
0
}