]>
Commit | Line | Data |
---|---|---|
89b8df51 | 1 | /* $OpenBSD: dns.c,v 1.44 2023/03/10 04:06:21 dtucker Exp $ */ |
37876e91 DM |
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 | ||
37876e91 DM |
28 | #include "includes.h" |
29 | ||
e3b60b52 DM |
30 | #include <sys/types.h> |
31 | #include <sys/socket.h> | |
32 | ||
b8fe89c4 | 33 | #include <netdb.h> |
ded319cc | 34 | #include <stdarg.h> |
a7a73ee3 | 35 | #include <stdio.h> |
e3476ed0 | 36 | #include <string.h> |
8668706d | 37 | #include <stdlib.h> |
37876e91 DM |
38 | |
39 | #include "xmalloc.h" | |
1129dcfc | 40 | #include "sshkey.h" |
41 | #include "ssherr.h" | |
37876e91 DM |
42 | #include "dns.h" |
43 | #include "log.h" | |
56d1c83c | 44 | #include "digest.h" |
37876e91 | 45 | |
541667fe | 46 | static const char * const errset_text[] = { |
37876e91 DM |
47 | "success", /* 0 ERRSET_SUCCESS */ |
48 | "out of memory", /* 1 ERRSET_NOMEMORY */ | |
49 | "general failure", /* 2 ERRSET_FAIL */ | |
50 | "invalid parameter", /* 3 ERRSET_INVAL */ | |
51 | "name does not exist", /* 4 ERRSET_NONAME */ | |
52 | "data does not exist", /* 5 ERRSET_NODATA */ | |
53 | }; | |
54 | ||
55 | static const char * | |
3f9fdc71 | 56 | dns_result_totext(unsigned int res) |
37876e91 | 57 | { |
3f9fdc71 | 58 | switch (res) { |
37876e91 DM |
59 | case ERRSET_SUCCESS: |
60 | return errset_text[ERRSET_SUCCESS]; | |
61 | case ERRSET_NOMEMORY: | |
62 | return errset_text[ERRSET_NOMEMORY]; | |
63 | case ERRSET_FAIL: | |
64 | return errset_text[ERRSET_FAIL]; | |
65 | case ERRSET_INVAL: | |
66 | return errset_text[ERRSET_INVAL]; | |
67 | case ERRSET_NONAME: | |
68 | return errset_text[ERRSET_NONAME]; | |
69 | case ERRSET_NODATA: | |
70 | return errset_text[ERRSET_NODATA]; | |
71 | default: | |
72 | return "unknown error"; | |
73 | } | |
74 | } | |
37876e91 DM |
75 | |
76 | /* | |
77 | * Read SSHFP parameters from key buffer. | |
b75a80fa | 78 | * Caller must free digest which is allocated by sshkey_fingerprint_raw(). |
37876e91 DM |
79 | */ |
80 | static int | |
81 | dns_read_key(u_int8_t *algorithm, u_int8_t *digest_type, | |
1129dcfc | 82 | u_char **digest, size_t *digest_len, struct sshkey *key) |
37876e91 | 83 | { |
1129dcfc | 84 | int r, success = 0; |
56d1c83c | 85 | int fp_alg = -1; |
37876e91 DM |
86 | |
87 | switch (key->type) { | |
88 | case KEY_RSA: | |
89 | *algorithm = SSHFP_KEY_RSA; | |
90 | break; | |
91 | case KEY_DSA: | |
92 | *algorithm = SSHFP_KEY_DSA; | |
3bde12ae DM |
93 | break; |
94 | case KEY_ECDSA: | |
95 | *algorithm = SSHFP_KEY_ECDSA; | |
37876e91 | 96 | break; |
16cd3928 DM |
97 | case KEY_ED25519: |
98 | *algorithm = SSHFP_KEY_ED25519; | |
16cd3928 | 99 | break; |
1b11ea7c | 100 | case KEY_XMSS: |
101 | *algorithm = SSHFP_KEY_XMSS; | |
1b11ea7c | 102 | break; |
37876e91 | 103 | default: |
65712490 | 104 | *algorithm = SSHFP_KEY_RESERVED; /* 0 */ |
3bde12ae DM |
105 | } |
106 | ||
107 | switch (*digest_type) { | |
108 | case SSHFP_HASH_SHA1: | |
56d1c83c | 109 | fp_alg = SSH_DIGEST_SHA1; |
3bde12ae DM |
110 | break; |
111 | case SSHFP_HASH_SHA256: | |
56d1c83c | 112 | fp_alg = SSH_DIGEST_SHA256; |
3bde12ae DM |
113 | break; |
114 | default: | |
115 | *digest_type = SSHFP_HASH_RESERVED; /* 0 */ | |
37876e91 DM |
116 | } |
117 | ||
3bde12ae | 118 | if (*algorithm && *digest_type) { |
1129dcfc | 119 | if ((r = sshkey_fingerprint_raw(key, fp_alg, digest, |
120 | digest_len)) != 0) | |
816036f1 | 121 | fatal_fr(r, "sshkey_fingerprint_raw"); |
37876e91 DM |
122 | success = 1; |
123 | } else { | |
37876e91 DM |
124 | *digest = NULL; |
125 | *digest_len = 0; | |
37876e91 DM |
126 | } |
127 | ||
128 | return success; | |
129 | } | |
130 | ||
131 | /* | |
132 | * Read SSHFP parameters from rdata buffer. | |
133 | */ | |
134 | static int | |
135 | dns_read_rdata(u_int8_t *algorithm, u_int8_t *digest_type, | |
1129dcfc | 136 | u_char **digest, size_t *digest_len, u_char *rdata, int rdata_len) |
37876e91 DM |
137 | { |
138 | int success = 0; | |
139 | ||
140 | *algorithm = SSHFP_KEY_RESERVED; | |
141 | *digest_type = SSHFP_HASH_RESERVED; | |
142 | ||
143 | if (rdata_len >= 2) { | |
144 | *algorithm = rdata[0]; | |
145 | *digest_type = rdata[1]; | |
146 | *digest_len = rdata_len - 2; | |
147 | ||
148 | if (*digest_len > 0) { | |
ce445b0e | 149 | *digest = xmalloc(*digest_len); |
37876e91 DM |
150 | memcpy(*digest, rdata + 2, *digest_len); |
151 | } else { | |
5996294a | 152 | *digest = (u_char *)xstrdup(""); |
37876e91 DM |
153 | } |
154 | ||
155 | success = 1; | |
156 | } | |
157 | ||
158 | return success; | |
159 | } | |
160 | ||
a31c929f DM |
161 | /* |
162 | * Check if hostname is numerical. | |
163 | * Returns -1 if hostname is numeric, 0 otherwise | |
164 | */ | |
165 | static int | |
166 | is_numeric_hostname(const char *hostname) | |
167 | { | |
168 | struct addrinfo hints, *ai; | |
169 | ||
30ac73bc DT |
170 | /* |
171 | * We shouldn't ever get a null host but if we do then log an error | |
172 | * and return -1 which stops DNS key fingerprint processing. | |
173 | */ | |
174 | if (hostname == NULL) { | |
175 | error("is_numeric_hostname called with NULL hostname"); | |
176 | return -1; | |
177 | } | |
178 | ||
a31c929f DM |
179 | memset(&hints, 0, sizeof(hints)); |
180 | hints.ai_socktype = SOCK_DGRAM; | |
181 | hints.ai_flags = AI_NUMERICHOST; | |
182 | ||
30ac73bc | 183 | if (getaddrinfo(hostname, NULL, &hints, &ai) == 0) { |
a31c929f DM |
184 | freeaddrinfo(ai); |
185 | return -1; | |
186 | } | |
187 | ||
188 | return 0; | |
189 | } | |
37876e91 DM |
190 | |
191 | /* | |
192 | * Verify the given hostname, address and host key using DNS. | |
a8e06cef | 193 | * Returns 0 if lookup succeeds, -1 otherwise |
37876e91 DM |
194 | */ |
195 | int | |
196 | verify_host_key_dns(const char *hostname, struct sockaddr *address, | |
1129dcfc | 197 | struct sshkey *hostkey, int *flags) |
37876e91 | 198 | { |
eccb9de7 | 199 | u_int counter; |
37876e91 DM |
200 | int result; |
201 | struct rrsetinfo *fingerprints = NULL; | |
37876e91 DM |
202 | |
203 | u_int8_t hostkey_algorithm; | |
37876e91 | 204 | u_char *hostkey_digest; |
1129dcfc | 205 | size_t hostkey_digest_len; |
37876e91 DM |
206 | |
207 | u_int8_t dnskey_algorithm; | |
208 | u_int8_t dnskey_digest_type; | |
209 | u_char *dnskey_digest; | |
1129dcfc | 210 | size_t dnskey_digest_len; |
37876e91 | 211 | |
150b5574 | 212 | *flags = 0; |
37876e91 | 213 | |
319550a5 | 214 | debug3("verify_host_key_dns"); |
37876e91 DM |
215 | if (hostkey == NULL) |
216 | fatal("No key to look up!"); | |
217 | ||
a31c929f DM |
218 | if (is_numeric_hostname(hostname)) { |
219 | debug("skipped DNS lookup for numerical hostname"); | |
220 | return -1; | |
221 | } | |
222 | ||
37876e91 DM |
223 | result = getrrsetbyname(hostname, DNS_RDATACLASS_IN, |
224 | DNS_RDATATYPE_SSHFP, 0, &fingerprints); | |
225 | if (result) { | |
226 | verbose("DNS lookup error: %s", dns_result_totext(result)); | |
150b5574 | 227 | return -1; |
37876e91 DM |
228 | } |
229 | ||
150b5574 DM |
230 | if (fingerprints->rri_flags & RRSET_VALIDATED) { |
231 | *flags |= DNS_VERIFY_SECURE; | |
232 | debug("found %d secure fingerprints in DNS", | |
233 | fingerprints->rri_nrdatas); | |
234 | } else { | |
235 | debug("found %d insecure fingerprints in DNS", | |
236 | fingerprints->rri_nrdatas); | |
37876e91 | 237 | } |
37876e91 | 238 | |
150b5574 DM |
239 | if (fingerprints->rri_nrdatas) |
240 | *flags |= DNS_VERIFY_FOUND; | |
241 | ||
80163907 | 242 | for (counter = 0; counter < fingerprints->rri_nrdatas; counter++) { |
37876e91 DM |
243 | /* |
244 | * Extract the key from the answer. Ignore any badly | |
245 | * formatted fingerprints. | |
246 | */ | |
247 | if (!dns_read_rdata(&dnskey_algorithm, &dnskey_digest_type, | |
248 | &dnskey_digest, &dnskey_digest_len, | |
249 | fingerprints->rri_rdatas[counter].rdi_data, | |
250 | fingerprints->rri_rdatas[counter].rdi_length)) { | |
251 | verbose("Error parsing fingerprint from DNS."); | |
252 | continue; | |
253 | } | |
b75a80fa | 254 | debug3_f("checking SSHFP type %d fptype %d", dnskey_algorithm, |
255 | dnskey_digest_type); | |
256 | ||
257 | /* Calculate host key fingerprint. */ | |
258 | if (!dns_read_key(&hostkey_algorithm, &dnskey_digest_type, | |
259 | &hostkey_digest, &hostkey_digest_len, hostkey)) { | |
260 | error("Error calculating key fingerprint."); | |
89b8df51 | 261 | free(dnskey_digest); |
b75a80fa | 262 | freerrset(fingerprints); |
263 | return -1; | |
3bde12ae DM |
264 | } |
265 | ||
37876e91 DM |
266 | /* Check if the current key is the same as the given key */ |
267 | if (hostkey_algorithm == dnskey_algorithm && | |
b75a80fa | 268 | hostkey_digest_len == dnskey_digest_len) { |
269 | if (timingsafe_bcmp(hostkey_digest, dnskey_digest, | |
270 | hostkey_digest_len) == 0) { | |
271 | debug_f("matched SSHFP type %d fptype %d", | |
272 | dnskey_algorithm, dnskey_digest_type); | |
150b5574 | 273 | *flags |= DNS_VERIFY_MATCH; |
b75a80fa | 274 | } else { |
275 | debug_f("failed SSHFP type %d fptype %d", | |
276 | dnskey_algorithm, dnskey_digest_type); | |
277 | *flags |= DNS_VERIFY_FAILED; | |
278 | } | |
37876e91 | 279 | } |
a627d42e | 280 | free(dnskey_digest); |
b75a80fa | 281 | free(hostkey_digest); /* from sshkey_fingerprint_raw() */ |
37876e91 DM |
282 | } |
283 | ||
aea59a0d | 284 | freerrset(fingerprints); |
285 | ||
b75a80fa | 286 | /* If any fingerprint failed to validate, return failure. */ |
287 | if (*flags & DNS_VERIFY_FAILED) | |
288 | *flags &= ~DNS_VERIFY_MATCH; | |
289 | ||
aea59a0d | 290 | if (*flags & DNS_VERIFY_FOUND) |
150b5574 DM |
291 | if (*flags & DNS_VERIFY_MATCH) |
292 | debug("matching host key fingerprint found in DNS"); | |
293 | else | |
294 | debug("mismatching host key fingerprint found in DNS"); | |
aea59a0d | 295 | else |
150b5574 | 296 | debug("no host key fingerprint found in DNS"); |
37876e91 | 297 | |
150b5574 | 298 | return 0; |
37876e91 DM |
299 | } |
300 | ||
37876e91 DM |
301 | /* |
302 | * Export the fingerprint of a key as a DNS resource record | |
303 | */ | |
304 | int | |
d651f5c9 | 305 | export_dns_rr(const char *hostname, struct sshkey *key, FILE *f, int generic, |
306 | int alg) | |
37876e91 DM |
307 | { |
308 | u_int8_t rdata_pubkey_algorithm = 0; | |
3bde12ae DM |
309 | u_int8_t rdata_digest_type = SSHFP_HASH_RESERVED; |
310 | u_int8_t dtype; | |
37876e91 | 311 | u_char *rdata_digest; |
1129dcfc | 312 | size_t i, rdata_digest_len; |
37876e91 DM |
313 | int success = 0; |
314 | ||
3bde12ae | 315 | for (dtype = SSHFP_HASH_SHA1; dtype < SSHFP_HASH_MAX; dtype++) { |
d651f5c9 | 316 | if (alg != -1 && dtype != alg) |
317 | continue; | |
3bde12ae DM |
318 | rdata_digest_type = dtype; |
319 | if (dns_read_key(&rdata_pubkey_algorithm, &rdata_digest_type, | |
320 | &rdata_digest, &rdata_digest_len, key)) { | |
321 | if (generic) { | |
1129dcfc | 322 | fprintf(f, "%s IN TYPE%d \\# %zu %02x %02x ", |
3bde12ae DM |
323 | hostname, DNS_RDATATYPE_SSHFP, |
324 | 2 + rdata_digest_len, | |
325 | rdata_pubkey_algorithm, rdata_digest_type); | |
326 | } else { | |
327 | fprintf(f, "%s IN SSHFP %d %d ", hostname, | |
328 | rdata_pubkey_algorithm, rdata_digest_type); | |
329 | } | |
330 | for (i = 0; i < rdata_digest_len; i++) | |
331 | fprintf(f, "%02x", rdata_digest[i]); | |
332 | fprintf(f, "\n"); | |
9ce86c92 | 333 | free(rdata_digest); /* from sshkey_fingerprint_raw() */ |
3bde12ae DM |
334 | success = 1; |
335 | } | |
336 | } | |
37876e91 | 337 | |
3bde12ae DM |
338 | /* No SSHFP record was generated at all */ |
339 | if (success == 0) { | |
816036f1 | 340 | error_f("unsupported algorithm and/or digest_type"); |
37876e91 DM |
341 | } |
342 | ||
343 | return success; | |
344 | } |