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