xref: /freebsd/crypto/openssh/dns.c (revision 2574974648c68c738aec3ff96644d888d7913a37)
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 *
dns_result_totext(unsigned int res)55 dns_result_totext(unsigned int res)
56 {
57 	switch (res) {
58 	case ERRSET_SUCCESS:
59 		return errset_text[ERRSET_SUCCESS];
60 	case ERRSET_NOMEMORY:
61 		return errset_text[ERRSET_NOMEMORY];
62 	case ERRSET_FAIL:
63 		return errset_text[ERRSET_FAIL];
64 	case ERRSET_INVAL:
65 		return errset_text[ERRSET_INVAL];
66 	case ERRSET_NONAME:
67 		return errset_text[ERRSET_NONAME];
68 	case ERRSET_NODATA:
69 		return errset_text[ERRSET_NODATA];
70 	default:
71 		return "unknown error";
72 	}
73 }
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
dns_read_key(uint8_t * algorithm,uint8_t * digest_type,u_char ** digest,size_t * digest_len,struct sshkey * key)80 dns_read_key(uint8_t *algorithm, uint8_t *digest_type,
81     u_char **digest, size_t *digest_len, struct sshkey *key)
82 {
83 	int r, success = 0;
84 	int fp_alg = -1;
85 
86 	switch (key->type) {
87 	case KEY_RSA:
88 		*algorithm = SSHFP_KEY_RSA;
89 		break;
90 	case KEY_ECDSA:
91 		*algorithm = SSHFP_KEY_ECDSA;
92 		break;
93 	case KEY_ED25519:
94 		*algorithm = SSHFP_KEY_ED25519;
95 		break;
96 	default:
97 		*algorithm = SSHFP_KEY_RESERVED; /* 0 */
98 	}
99 
100 	switch (*digest_type) {
101 	case SSHFP_HASH_SHA1:
102 		fp_alg = SSH_DIGEST_SHA1;
103 		break;
104 	case SSHFP_HASH_SHA256:
105 		fp_alg = SSH_DIGEST_SHA256;
106 		break;
107 	default:
108 		*digest_type = SSHFP_HASH_RESERVED; /* 0 */
109 	}
110 
111 	if (*algorithm && *digest_type) {
112 		if ((r = sshkey_fingerprint_raw(key, fp_alg, digest,
113 		    digest_len)) != 0)
114 			fatal_fr(r, "sshkey_fingerprint_raw");
115 		success = 1;
116 	} else {
117 		*digest = NULL;
118 		*digest_len = 0;
119 	}
120 
121 	return success;
122 }
123 
124 /*
125  * Read SSHFP parameters from rdata buffer.
126  */
127 static int
dns_read_rdata(uint8_t * algorithm,uint8_t * digest_type,u_char ** digest,size_t * digest_len,u_char * rdata,int rdata_len)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 {
131 	int success = 0;
132 
133 	*algorithm = SSHFP_KEY_RESERVED;
134 	*digest_type = SSHFP_HASH_RESERVED;
135 
136 	if (rdata_len >= 2) {
137 		*algorithm = rdata[0];
138 		*digest_type = rdata[1];
139 		*digest_len = rdata_len - 2;
140 
141 		if (*digest_len > 0) {
142 			*digest = xmalloc(*digest_len);
143 			memcpy(*digest, rdata + 2, *digest_len);
144 		} else {
145 			*digest = (u_char *)xstrdup("");
146 		}
147 
148 		success = 1;
149 	}
150 
151 	return success;
152 }
153 
154 /*
155  * Check if hostname is numerical.
156  * Returns -1 if hostname is numeric, 0 otherwise
157  */
158 static int
is_numeric_hostname(const char * hostname)159 is_numeric_hostname(const char *hostname)
160 {
161 	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 	if (hostname == NULL) {
168 		error("is_numeric_hostname called with NULL hostname");
169 		return -1;
170 	}
171 
172 	memset(&hints, 0, sizeof(hints));
173 	hints.ai_socktype = SOCK_DGRAM;
174 	hints.ai_flags = AI_NUMERICHOST;
175 
176 	if (getaddrinfo(hostname, NULL, &hints, &ai) == 0) {
177 		freeaddrinfo(ai);
178 		return -1;
179 	}
180 
181 	return 0;
182 }
183 
184 /*
185  * Verify the given hostname, address and host key using DNS.
186  * Returns 0 if lookup succeeds, -1 otherwise
187  */
188 int
verify_host_key_dns(const char * hostname,struct sockaddr * address,struct sshkey * hostkey,int * flags)189 verify_host_key_dns(const char *hostname, struct sockaddr *address,
190     struct sshkey *hostkey, int *flags)
191 {
192 	u_int counter;
193 	int result;
194 	struct rrsetinfo *fingerprints = NULL;
195 
196 	uint8_t hostkey_algorithm;
197 	u_char *hostkey_digest;
198 	size_t hostkey_digest_len;
199 
200 	uint8_t dnskey_algorithm;
201 	uint8_t dnskey_digest_type;
202 	u_char *dnskey_digest;
203 	size_t dnskey_digest_len;
204 
205 	*flags = 0;
206 
207 	debug3("verify_host_key_dns");
208 	if (hostkey == NULL)
209 		fatal("No key to look up!");
210 
211 	if (is_numeric_hostname(hostname)) {
212 		debug("skipped DNS lookup for numerical hostname");
213 		return -1;
214 	}
215 
216 	result = getrrsetbyname(hostname, DNS_RDATACLASS_IN,
217 	    DNS_RDATATYPE_SSHFP, 0, &fingerprints);
218 	if (result) {
219 		verbose("DNS lookup error: %s", dns_result_totext(result));
220 		return -1;
221 	}
222 
223 	if (fingerprints->rri_flags & RRSET_VALIDATED) {
224 		*flags |= DNS_VERIFY_SECURE;
225 		debug("found %d secure fingerprints in DNS",
226 		    fingerprints->rri_nrdatas);
227 	} else {
228 		debug("found %d insecure fingerprints in DNS",
229 		    fingerprints->rri_nrdatas);
230 	}
231 
232 	if (fingerprints->rri_nrdatas)
233 		*flags |= DNS_VERIFY_FOUND;
234 
235 	for (counter = 0; counter < fingerprints->rri_nrdatas; counter++) {
236 		/*
237 		 * Extract the key from the answer. Ignore any badly
238 		 * formatted fingerprints.
239 		 */
240 		if (!dns_read_rdata(&dnskey_algorithm, &dnskey_digest_type,
241 		    &dnskey_digest, &dnskey_digest_len,
242 		    fingerprints->rri_rdatas[counter].rdi_data,
243 		    fingerprints->rri_rdatas[counter].rdi_length)) {
244 			verbose("Error parsing fingerprint from DNS.");
245 			continue;
246 		}
247 		debug3_f("checking SSHFP type %d fptype %d", dnskey_algorithm,
248 		    dnskey_digest_type);
249 
250 		/* Calculate host key fingerprint. */
251 		if (!dns_read_key(&hostkey_algorithm, &dnskey_digest_type,
252 		    &hostkey_digest, &hostkey_digest_len, hostkey)) {
253 			error("Error calculating key fingerprint.");
254 			free(dnskey_digest);
255 			freerrset(fingerprints);
256 			return -1;
257 		}
258 
259 		/* Check if the current key is the same as the given key */
260 		if (hostkey_algorithm == dnskey_algorithm &&
261 		    hostkey_digest_len == dnskey_digest_len) {
262 			if (timingsafe_bcmp(hostkey_digest, dnskey_digest,
263 			    hostkey_digest_len) == 0) {
264 				debug_f("matched SSHFP type %d fptype %d",
265 				    dnskey_algorithm, dnskey_digest_type);
266 				*flags |= DNS_VERIFY_MATCH;
267 			} else {
268 				debug_f("failed SSHFP type %d fptype %d",
269 				    dnskey_algorithm, dnskey_digest_type);
270 				*flags |= DNS_VERIFY_FAILED;
271 			}
272 		}
273 		free(dnskey_digest);
274 		free(hostkey_digest); /* from sshkey_fingerprint_raw() */
275 	}
276 
277 	freerrset(fingerprints);
278 
279 	/* If any fingerprint failed to validate, return failure. */
280 	if (*flags & DNS_VERIFY_FAILED)
281 		*flags &= ~DNS_VERIFY_MATCH;
282 
283 	if (*flags & DNS_VERIFY_FOUND)
284 		if (*flags & DNS_VERIFY_MATCH)
285 			debug("matching host key fingerprint found in DNS");
286 		else
287 			debug("mismatching host key fingerprint found in DNS");
288 	else
289 		debug("no host key fingerprint found in DNS");
290 
291 	return 0;
292 }
293 
294 /*
295  * Export the fingerprint of a key as a DNS resource record
296  */
297 int
export_dns_rr(const char * hostname,struct sshkey * key,FILE * f,int generic,int alg)298 export_dns_rr(const char *hostname, struct sshkey *key, FILE *f, int generic,
299     int alg)
300 {
301 	uint8_t rdata_pubkey_algorithm = 0;
302 	uint8_t rdata_digest_type = SSHFP_HASH_RESERVED;
303 	uint8_t dtype;
304 	u_char *rdata_digest;
305 	size_t i, rdata_digest_len;
306 	int success = 0;
307 
308 	for (dtype = SSHFP_HASH_SHA1; dtype < SSHFP_HASH_MAX; dtype++) {
309 		if (alg != -1 && dtype != alg)
310 			continue;
311 		rdata_digest_type = dtype;
312 		if (dns_read_key(&rdata_pubkey_algorithm, &rdata_digest_type,
313 		    &rdata_digest, &rdata_digest_len, key)) {
314 			if (generic) {
315 				fprintf(f, "%s IN TYPE%d \\# %zu %02x %02x ",
316 				    hostname, DNS_RDATATYPE_SSHFP,
317 				    2 + rdata_digest_len,
318 				    rdata_pubkey_algorithm, rdata_digest_type);
319 			} else {
320 				fprintf(f, "%s IN SSHFP %d %d ", hostname,
321 				    rdata_pubkey_algorithm, rdata_digest_type);
322 			}
323 			for (i = 0; i < rdata_digest_len; i++)
324 				fprintf(f, "%02x", rdata_digest[i]);
325 			fprintf(f, "\n");
326 			free(rdata_digest); /* from sshkey_fingerprint_raw() */
327 			success = 1;
328 		}
329 	}
330 
331 	/* No SSHFP record was generated at all */
332 	if (success == 0) {
333 		error_f("unsupported algorithm and/or digest_type");
334 	}
335 
336 	return success;
337 }
338