Upgrading to GitLab 12.1.0.

dns.c 9.53 KB
Newer Older
1
/* $OpenBSD: dns.c,v 1.38 2018/02/23 15:58:37 markus Exp $ */
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

/*
 * Copyright (c) 2003 Wesley Griffin. All rights reserved.
 * Copyright (c) 2003 Jakob Schlyter. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "includes.h"

30 31 32
#include <sys/types.h>
#include <sys/socket.h>

33
#include <netdb.h>
34
#include <stdarg.h>
35
#include <stdio.h>
36
#include <string.h>
37 38
#include <stdarg.h>
#include <stdlib.h>
39 40

#include "xmalloc.h"
djm@openbsd.org's avatar
djm@openbsd.org committed
41 42
#include "sshkey.h"
#include "ssherr.h"
43 44
#include "dns.h"
#include "log.h"
djm@openbsd.org's avatar
djm@openbsd.org committed
45
#include "digest.h"
46 47 48 49 50 51 52 53 54 55 56

static const char *errset_text[] = {
	"success",		/* 0 ERRSET_SUCCESS */
	"out of memory",	/* 1 ERRSET_NOMEMORY */
	"general failure",	/* 2 ERRSET_FAIL */
	"invalid parameter",	/* 3 ERRSET_INVAL */
	"name does not exist",	/* 4 ERRSET_NONAME */
	"data does not exist",	/* 5 ERRSET_NODATA */
};

static const char *
57
dns_result_totext(unsigned int res)
58
{
59
	switch (res) {
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
	case ERRSET_SUCCESS:
		return errset_text[ERRSET_SUCCESS];
	case ERRSET_NOMEMORY:
		return errset_text[ERRSET_NOMEMORY];
	case ERRSET_FAIL:
		return errset_text[ERRSET_FAIL];
	case ERRSET_INVAL:
		return errset_text[ERRSET_INVAL];
	case ERRSET_NONAME:
		return errset_text[ERRSET_NONAME];
	case ERRSET_NODATA:
		return errset_text[ERRSET_NODATA];
	default:
		return "unknown error";
	}
}

/*
 * Read SSHFP parameters from key buffer.
 */
static int
dns_read_key(u_int8_t *algorithm, u_int8_t *digest_type,
djm@openbsd.org's avatar
djm@openbsd.org committed
82
    u_char **digest, size_t *digest_len, struct sshkey *key)
83
{
djm@openbsd.org's avatar
djm@openbsd.org committed
84
	int r, success = 0;
djm@openbsd.org's avatar
djm@openbsd.org committed
85
	int fp_alg = -1;
86 87 88 89

	switch (key->type) {
	case KEY_RSA:
		*algorithm = SSHFP_KEY_RSA;
90 91
		if (!*digest_type)
			*digest_type = SSHFP_HASH_SHA1;
92 93 94
		break;
	case KEY_DSA:
		*algorithm = SSHFP_KEY_DSA;
95 96 97 98 99 100 101
		if (!*digest_type)
			*digest_type = SSHFP_HASH_SHA1;
		break;
	case KEY_ECDSA:
		*algorithm = SSHFP_KEY_ECDSA;
		if (!*digest_type)
			*digest_type = SSHFP_HASH_SHA256;
102
		break;
103 104 105 106 107
	case KEY_ED25519:
		*algorithm = SSHFP_KEY_ED25519;
		if (!*digest_type)
			*digest_type = SSHFP_HASH_SHA256;
		break;
108 109 110 111 112
	case KEY_XMSS:
		*algorithm = SSHFP_KEY_XMSS;
		if (!*digest_type)
			*digest_type = SSHFP_HASH_SHA256;
		break;
113
	default:
114
		*algorithm = SSHFP_KEY_RESERVED; /* 0 */
115 116 117 118 119
		*digest_type = SSHFP_HASH_RESERVED; /* 0 */
	}

	switch (*digest_type) {
	case SSHFP_HASH_SHA1:
djm@openbsd.org's avatar
djm@openbsd.org committed
120
		fp_alg = SSH_DIGEST_SHA1;
121 122
		break;
	case SSHFP_HASH_SHA256:
djm@openbsd.org's avatar
djm@openbsd.org committed
123
		fp_alg = SSH_DIGEST_SHA256;
124 125 126
		break;
	default:
		*digest_type = SSHFP_HASH_RESERVED; /* 0 */
127 128
	}

129
	if (*algorithm && *digest_type) {
djm@openbsd.org's avatar
djm@openbsd.org committed
130 131 132 133
		if ((r = sshkey_fingerprint_raw(key, fp_alg, digest,
		    digest_len)) != 0)
			fatal("%s: sshkey_fingerprint_raw: %s", __func__,
			   ssh_err(r));
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
		success = 1;
	} else {
		*digest = NULL;
		*digest_len = 0;
		success = 0;
	}

	return success;
}

/*
 * Read SSHFP parameters from rdata buffer.
 */
static int
dns_read_rdata(u_int8_t *algorithm, u_int8_t *digest_type,
djm@openbsd.org's avatar
djm@openbsd.org committed
149
    u_char **digest, size_t *digest_len, u_char *rdata, int rdata_len)
150 151 152 153 154 155 156 157 158 159 160 161
{
	int success = 0;

	*algorithm = SSHFP_KEY_RESERVED;
	*digest_type = SSHFP_HASH_RESERVED;

	if (rdata_len >= 2) {
		*algorithm = rdata[0];
		*digest_type = rdata[1];
		*digest_len = rdata_len - 2;

		if (*digest_len > 0) {
deraadt@openbsd.org's avatar
deraadt@openbsd.org committed
162
			*digest = xmalloc(*digest_len);
163 164
			memcpy(*digest, rdata + 2, *digest_len);
		} else {
165
			*digest = (u_char *)xstrdup("");
166 167 168 169 170 171 172 173
		}

		success = 1;
	}

	return success;
}

174 175 176 177 178 179 180 181 182
/*
 * Check if hostname is numerical.
 * Returns -1 if hostname is numeric, 0 otherwise
 */
static int
is_numeric_hostname(const char *hostname)
{
	struct addrinfo hints, *ai;

183 184 185 186 187 188 189 190 191
	/*
	 * We shouldn't ever get a null host but if we do then log an error
	 * and return -1 which stops DNS key fingerprint processing.
	 */
	if (hostname == NULL) {
		error("is_numeric_hostname called with NULL hostname");
		return -1;
	}

192 193 194 195
	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_NUMERICHOST;

196
	if (getaddrinfo(hostname, NULL, &hints, &ai) == 0) {
197 198 199 200 201 202
		freeaddrinfo(ai);
		return -1;
	}

	return 0;
}
203 204 205

/*
 * Verify the given hostname, address and host key using DNS.
206
 * Returns 0 if lookup succeeds, -1 otherwise
207 208 209
 */
int
verify_host_key_dns(const char *hostname, struct sockaddr *address,
djm@openbsd.org's avatar
djm@openbsd.org committed
210
    struct sshkey *hostkey, int *flags)
211
{
212
	u_int counter;
213
	int result;
214
	unsigned int rrset_flags = 0;
215 216 217
	struct rrsetinfo *fingerprints = NULL;

	u_int8_t hostkey_algorithm;
218
	u_int8_t hostkey_digest_type = SSHFP_HASH_RESERVED;
219
	u_char *hostkey_digest;
djm@openbsd.org's avatar
djm@openbsd.org committed
220
	size_t hostkey_digest_len;
221 222 223 224

	u_int8_t dnskey_algorithm;
	u_int8_t dnskey_digest_type;
	u_char *dnskey_digest;
djm@openbsd.org's avatar
djm@openbsd.org committed
225
	size_t dnskey_digest_len;
226

227
	*flags = 0;
228

229
	debug3("verify_host_key_dns");
230 231 232
	if (hostkey == NULL)
		fatal("No key to look up!");

233 234 235 236 237
	if (is_numeric_hostname(hostname)) {
		debug("skipped DNS lookup for numerical hostname");
		return -1;
	}

238 239 240 241 242 243 244 245 246 247
	/*
	 * Original getrrsetbyname function, found on OpenBSD for example,
	 * doesn't accept any flag and prerequisite for obtaining AD bit in
	 * DNS response is set by "options edns0" in resolv.conf.
	 *
	 * Our version is more clever and use RRSET_FORCE_EDNS0 flag.
	 */
#ifndef HAVE_GETRRSETBYNAME
	rrset_flags |= RRSET_FORCE_EDNS0;
#endif
248
	result = getrrsetbyname(hostname, DNS_RDATACLASS_IN,
249 250
	    DNS_RDATATYPE_SSHFP, rrset_flags, &fingerprints);

251 252
	if (result) {
		verbose("DNS lookup error: %s", dns_result_totext(result));
253
		return -1;
254 255
	}

256 257 258 259 260 261 262
	if (fingerprints->rri_flags & RRSET_VALIDATED) {
		*flags |= DNS_VERIFY_SECURE;
		debug("found %d secure fingerprints in DNS",
		    fingerprints->rri_nrdatas);
	} else {
		debug("found %d insecure fingerprints in DNS",
		    fingerprints->rri_nrdatas);
263 264
	}

265
	/* Initialize default host key parameters */
266 267 268
	if (!dns_read_key(&hostkey_algorithm, &hostkey_digest_type,
	    &hostkey_digest, &hostkey_digest_len, hostkey)) {
		error("Error calculating host key fingerprint.");
269
		freerrset(fingerprints);
270
		return -1;
271 272
	}

273 274 275
	if (fingerprints->rri_nrdatas)
		*flags |= DNS_VERIFY_FOUND;

276
	for (counter = 0; counter < fingerprints->rri_nrdatas; counter++) {
277 278 279 280 281 282 283 284 285 286 287 288
		/*
		 * Extract the key from the answer. Ignore any badly
		 * formatted fingerprints.
		 */
		if (!dns_read_rdata(&dnskey_algorithm, &dnskey_digest_type,
		    &dnskey_digest, &dnskey_digest_len,
		    fingerprints->rri_rdatas[counter].rdi_data,
		    fingerprints->rri_rdatas[counter].rdi_length)) {
			verbose("Error parsing fingerprint from DNS.");
			continue;
		}

289 290
		if (hostkey_digest_type != dnskey_digest_type) {
			hostkey_digest_type = dnskey_digest_type;
291
			free(hostkey_digest);
292 293 294 295 296 297 298 299 300 301 302

			/* Initialize host key parameters */
			if (!dns_read_key(&hostkey_algorithm,
			    &hostkey_digest_type, &hostkey_digest,
			    &hostkey_digest_len, hostkey)) {
				error("Error calculating key fingerprint.");
				freerrset(fingerprints);
				return -1;
			}
		}

303 304 305 306
		/* Check if the current key is the same as the given key */
		if (hostkey_algorithm == dnskey_algorithm &&
		    hostkey_digest_type == dnskey_digest_type) {
			if (hostkey_digest_len == dnskey_digest_len &&
307 308
			    timingsafe_bcmp(hostkey_digest, dnskey_digest,
			    hostkey_digest_len) == 0)
309
				*flags |= DNS_VERIFY_MATCH;
310
		}
311
		free(dnskey_digest);
312 313
	}

djm@openbsd.org's avatar
djm@openbsd.org committed
314 315 316 317
	free(hostkey_digest); /* from sshkey_fingerprint_raw() */
	freerrset(fingerprints);

	if (*flags & DNS_VERIFY_FOUND)
318 319 320 321
		if (*flags & DNS_VERIFY_MATCH)
			debug("matching host key fingerprint found in DNS");
		else
			debug("mismatching host key fingerprint found in DNS");
djm@openbsd.org's avatar
djm@openbsd.org committed
322
	else
323
		debug("no host key fingerprint found in DNS");
324

325
	return 0;
326 327 328 329 330 331
}

/*
 * Export the fingerprint of a key as a DNS resource record
 */
int
djm@openbsd.org's avatar
djm@openbsd.org committed
332
export_dns_rr(const char *hostname, struct sshkey *key, FILE *f, int generic)
333 334
{
	u_int8_t rdata_pubkey_algorithm = 0;
335 336
	u_int8_t rdata_digest_type = SSHFP_HASH_RESERVED;
	u_int8_t dtype;
337
	u_char *rdata_digest;
djm@openbsd.org's avatar
djm@openbsd.org committed
338
	size_t i, rdata_digest_len;
339 340
	int success = 0;

341 342 343 344 345
	for (dtype = SSHFP_HASH_SHA1; dtype < SSHFP_HASH_MAX; dtype++) {
		rdata_digest_type = dtype;
		if (dns_read_key(&rdata_pubkey_algorithm, &rdata_digest_type,
		    &rdata_digest, &rdata_digest_len, key)) {
			if (generic) {
djm@openbsd.org's avatar
djm@openbsd.org committed
346
				fprintf(f, "%s IN TYPE%d \\# %zu %02x %02x ",
347 348 349 350 351 352 353 354 355 356
				    hostname, DNS_RDATATYPE_SSHFP,
				    2 + rdata_digest_len,
				    rdata_pubkey_algorithm, rdata_digest_type);
			} else {
				fprintf(f, "%s IN SSHFP %d %d ", hostname,
				    rdata_pubkey_algorithm, rdata_digest_type);
			}
			for (i = 0; i < rdata_digest_len; i++)
				fprintf(f, "%02x", rdata_digest[i]);
			fprintf(f, "\n");
djm@openbsd.org's avatar
djm@openbsd.org committed
357
			free(rdata_digest); /* from sshkey_fingerprint_raw() */
358 359 360
			success = 1;
		}
	}
361

362 363 364
	/* No SSHFP record was generated at all */
	if (success == 0) {
		error("%s: unsupported algorithm and/or digest_type", __func__);
365 366 367 368
	}

	return success;
}