Skip to content

Commit 6f34498

Browse files
lxdictedaduh95
authored andcommitted
net: add support for resolving DNS CAA records
This adds support for DNS Certification Authority Authorization (RFC 8659) to Node.js. PR-URL: #35466 Fixes: #19239 Refs: #14713 Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent cfbbeea commit 6f34498

17 files changed

+421
-1
lines changed

deps/cares/cares.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
'src/ares__parse_into_addrinfo.c',
7676
'src/ares_parse_aaaa_reply.c',
7777
'src/ares_parse_a_reply.c',
78+
'src/ares_parse_caa_reply.c',
7879
'src/ares_parse_mx_reply.c',
7980
'src/ares_parse_naptr_reply.c',
8081
'src/ares_parse_ns_reply.c',

deps/cares/include/ares.h

+13
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,15 @@ struct ares_addr6ttl {
528528
int ttl;
529529
};
530530

531+
struct ares_caa_reply {
532+
struct ares_caa_reply *next;
533+
int critical;
534+
unsigned char *property;
535+
size_t plength; /* plength excludes null termination */
536+
unsigned char *value;
537+
size_t length; /* length excludes null termination */
538+
};
539+
531540
struct ares_srv_reply {
532541
struct ares_srv_reply *next;
533542
char *host;
@@ -637,6 +646,10 @@ CARES_EXTERN int ares_parse_aaaa_reply(const unsigned char *abuf,
637646
struct ares_addr6ttl *addrttls,
638647
int *naddrttls);
639648

649+
CARES_EXTERN int ares_parse_caa_reply(const unsigned char* abuf,
650+
int alen,
651+
struct ares_caa_reply** caa_out);
652+
640653
CARES_EXTERN int ares_parse_ptr_reply(const unsigned char *abuf,
641654
int alen,
642655
const void *addr,

deps/cares/include/nameser.h

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ typedef enum __ns_type {
8888
ns_t_maila = 254, /* Transfer mail agent records. */
8989
ns_t_any = 255, /* Wildcard match. */
9090
ns_t_zxfr = 256, /* BIND-specific, nonstandard. */
91+
ns_t_caa = 257, /* CA Authorization (RFC8659) */
9192
ns_t_max = 65536
9293
} ns_type;
9394

@@ -204,6 +205,7 @@ typedef enum __ns_rcode {
204205
#define T_AXFR ns_t_axfr
205206
#define T_MAILB ns_t_mailb
206207
#define T_MAILA ns_t_maila
208+
#define T_CAA ns_t_caa
207209
#define T_ANY ns_t_any
208210

209211
#endif /* HAVE_ARPA_NAMESER_COMPAT_H */

deps/cares/src/ares_data.c

+18
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ void ares_free_data(void *dataptr)
119119
ares_free(ptr->data.soa_reply.hostmaster);
120120
break;
121121

122+
case ARES_DATATYPE_CAA_REPLY:
123+
124+
if (ptr->data.caa_reply.next)
125+
next_data = ptr->data.caa_reply.next;
126+
if (ptr->data.caa_reply.property)
127+
ares_free(ptr->data.caa_reply.property);
128+
if (ptr->data.caa_reply.value)
129+
ares_free(ptr->data.caa_reply.value);
130+
break;
131+
122132
default:
123133
return;
124134
}
@@ -174,6 +184,14 @@ void *ares_malloc_data(ares_datatype type)
174184
ptr->data.txt_reply.length = 0;
175185
break;
176186

187+
case ARES_DATATYPE_CAA_REPLY:
188+
ptr->data.caa_reply.next = NULL;
189+
ptr->data.caa_reply.plength = 0;
190+
ptr->data.caa_reply.property = NULL;
191+
ptr->data.caa_reply.length = 0;
192+
ptr->data.caa_reply.value = NULL;
193+
break;
194+
177195
case ARES_DATATYPE_ADDR_NODE:
178196
ptr->data.addr_node.next = NULL;
179197
ptr->data.addr_node.family = 0;

deps/cares/src/ares_data.h

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ typedef enum {
3030
ARES_DATATYPE_OPTIONS, /* struct ares_options */
3131
#endif
3232
ARES_DATATYPE_ADDR_PORT_NODE, /* struct ares_addr_port_node - introduced in 1.11.0 */
33+
ARES_DATATYPE_CAA_REPLY, /* struct ares_caa_reply - introduced in 1.17 */
3334
ARES_DATATYPE_LAST /* not used - introduced in 1.7.0 */
3435
} ares_datatype;
3536

@@ -65,6 +66,7 @@ struct ares_data {
6566
struct ares_mx_reply mx_reply;
6667
struct ares_naptr_reply naptr_reply;
6768
struct ares_soa_reply soa_reply;
69+
struct ares_caa_reply caa_reply;
6870
} data;
6971
};
7072

deps/cares/src/ares_parse_caa_reply.c

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
2+
/* Copyright 2020 by <danny.sonnenschein@platynum.ch>
3+
*
4+
* Permission to use, copy, modify, and distribute this
5+
* software and its documentation for any purpose and without
6+
* fee is hereby granted, provided that the above copyright
7+
* notice appear in all copies and that both that copyright
8+
* notice and this permission notice appear in supporting
9+
* documentation, and that the name of M.I.T. not be used in
10+
* advertising or publicity pertaining to distribution of the
11+
* software without specific, written prior permission.
12+
* M.I.T. makes no representations about the suitability of
13+
* this software for any purpose. It is provided "as is"
14+
* without express or implied warranty.
15+
*/
16+
17+
#include "ares_setup.h"
18+
19+
#ifdef HAVE_NETINET_IN_H
20+
# include <netinet/in.h>
21+
#endif
22+
#ifdef HAVE_NETDB_H
23+
# include <netdb.h>
24+
#endif
25+
#ifdef HAVE_ARPA_INET_H
26+
# include <arpa/inet.h>
27+
#endif
28+
#ifdef HAVE_ARPA_NAMESER_H
29+
# include <arpa/nameser.h>
30+
#else
31+
# include "nameser.h"
32+
#endif
33+
#ifdef HAVE_ARPA_NAMESER_COMPAT_H
34+
# include <arpa/nameser_compat.h>
35+
#endif
36+
37+
#ifdef HAVE_STRINGS_H
38+
# include <strings.h>
39+
#endif
40+
41+
#include "ares.h"
42+
#include "ares_dns.h"
43+
#include "ares_data.h"
44+
#include "ares_private.h"
45+
46+
#ifndef T_CAA
47+
# define T_CAA 257 /* Certification Authority Authorization */
48+
#endif
49+
50+
int
51+
ares_parse_caa_reply (const unsigned char *abuf, int alen,
52+
struct ares_caa_reply **caa_out)
53+
{
54+
unsigned int qdcount, ancount, i;
55+
const unsigned char *aptr;
56+
const unsigned char *strptr;
57+
int status, rr_type, rr_class, rr_len;
58+
long len;
59+
char *hostname = NULL, *rr_name = NULL;
60+
struct ares_caa_reply *caa_head = NULL;
61+
struct ares_caa_reply *caa_last = NULL;
62+
struct ares_caa_reply *caa_curr;
63+
64+
/* Set *caa_out to NULL for all failure cases. */
65+
*caa_out = NULL;
66+
67+
/* Give up if abuf doesn't have room for a header. */
68+
if (alen < HFIXEDSZ)
69+
return ARES_EBADRESP;
70+
71+
/* Fetch the question and answer count from the header. */
72+
qdcount = DNS_HEADER_QDCOUNT (abuf);
73+
ancount = DNS_HEADER_ANCOUNT (abuf);
74+
if (qdcount != 1)
75+
return ARES_EBADRESP;
76+
if (ancount == 0)
77+
return ARES_ENODATA;
78+
79+
/* Expand the name from the question, and skip past the question. */
80+
aptr = abuf + HFIXEDSZ;
81+
status = ares_expand_name (aptr, abuf, alen, &hostname, &len);
82+
if (status != ARES_SUCCESS)
83+
return status;
84+
85+
if (aptr + len + QFIXEDSZ > abuf + alen)
86+
{
87+
ares_free (hostname);
88+
return ARES_EBADRESP;
89+
}
90+
aptr += len + QFIXEDSZ;
91+
92+
/* Examine each answer resource record (RR) in turn. */
93+
for (i = 0; i < ancount; i++)
94+
{
95+
/* Decode the RR up to the data field. */
96+
status = ares_expand_name (aptr, abuf, alen, &rr_name, &len);
97+
if (status != ARES_SUCCESS)
98+
{
99+
break;
100+
}
101+
aptr += len;
102+
if (aptr + RRFIXEDSZ > abuf + alen)
103+
{
104+
status = ARES_EBADRESP;
105+
break;
106+
}
107+
rr_type = DNS_RR_TYPE (aptr);
108+
rr_class = DNS_RR_CLASS (aptr);
109+
rr_len = DNS_RR_LEN (aptr);
110+
aptr += RRFIXEDSZ;
111+
if (aptr + rr_len > abuf + alen)
112+
{
113+
status = ARES_EBADRESP;
114+
break;
115+
}
116+
117+
/* Check if we are really looking at a CAA record */
118+
if ((rr_class == C_IN || rr_class == C_CHAOS) && rr_type == T_CAA)
119+
{
120+
strptr = aptr;
121+
122+
/* Allocate storage for this CAA answer appending it to the list */
123+
caa_curr = ares_malloc_data(ARES_DATATYPE_CAA_REPLY);
124+
if (!caa_curr)
125+
{
126+
status = ARES_ENOMEM;
127+
break;
128+
}
129+
if (caa_last)
130+
{
131+
caa_last->next = caa_curr;
132+
}
133+
else
134+
{
135+
caa_head = caa_curr;
136+
}
137+
caa_last = caa_curr;
138+
if (rr_len < 2)
139+
{
140+
status = ARES_EBADRESP;
141+
break;
142+
}
143+
caa_curr->critical = (int)*strptr++;
144+
caa_curr->plength = (int)*strptr++;
145+
if (caa_curr->plength <= 0 || (int)caa_curr->plength >= rr_len - 2)
146+
{
147+
status = ARES_EBADRESP;
148+
break;
149+
}
150+
caa_curr->property = ares_malloc (caa_curr->plength + 1/* Including null byte */);
151+
if (caa_curr->property == NULL)
152+
{
153+
status = ARES_ENOMEM;
154+
break;
155+
}
156+
memcpy ((char *) caa_curr->property, strptr, caa_curr->plength);
157+
/* Make sure we NULL-terminate */
158+
caa_curr->property[caa_curr->plength] = 0;
159+
strptr += caa_curr->plength;
160+
161+
caa_curr->length = rr_len - caa_curr->plength - 2;
162+
if (caa_curr->length <= 0)
163+
{
164+
status = ARES_EBADRESP;
165+
break;
166+
}
167+
caa_curr->value = ares_malloc (caa_curr->length + 1/* Including null byte */);
168+
if (caa_curr->value == NULL)
169+
{
170+
status = ARES_ENOMEM;
171+
break;
172+
}
173+
memcpy ((char *) caa_curr->value, strptr, caa_curr->length);
174+
/* Make sure we NULL-terminate */
175+
caa_curr->value[caa_curr->length] = 0;
176+
}
177+
178+
/* Propagate any failures */
179+
if (status != ARES_SUCCESS)
180+
{
181+
break;
182+
}
183+
184+
/* Don't lose memory in the next iteration */
185+
ares_free (rr_name);
186+
rr_name = NULL;
187+
188+
/* Move on to the next record */
189+
aptr += rr_len;
190+
}
191+
192+
if (hostname)
193+
ares_free (hostname);
194+
if (rr_name)
195+
ares_free (rr_name);
196+
197+
/* clean up on error */
198+
if (status != ARES_SUCCESS)
199+
{
200+
if (caa_head)
201+
ares_free_data (caa_head);
202+
return status;
203+
}
204+
205+
/* everything looks fine, return the data */
206+
*caa_out = caa_head;
207+
208+
return ARES_SUCCESS;
209+
}

doc/api/dns.md

+35
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ The following methods from the `dns` module are available:
8181
* [`resolver.resolve4()`][`dns.resolve4()`]
8282
* [`resolver.resolve6()`][`dns.resolve6()`]
8383
* [`resolver.resolveAny()`][`dns.resolveAny()`]
84+
* [`resolver.resolveCaa()`][`dns.resolveCaa()`]
8485
* [`resolver.resolveCname()`][`dns.resolveCname()`]
8586
* [`resolver.resolveMx()`][`dns.resolveMx()`]
8687
* [`resolver.resolveNaptr()`][`dns.resolveNaptr()`]
@@ -289,6 +290,7 @@ records. The type and structure of individual results varies based on `rrtype`:
289290
| `'A'` | IPv4 addresses (default) | {string} | [`dns.resolve4()`][] |
290291
| `'AAAA'` | IPv6 addresses | {string} | [`dns.resolve6()`][] |
291292
| `'ANY'` | any records | {Object} | [`dns.resolveAny()`][] |
293+
| `'CAA'` | CA authorization records | {Object} | [`dns.resolveCaa()`][] |
292294
| `'CNAME'` | canonical name records | {string} | [`dns.resolveCname()`][] |
293295
| `'MX'` | mail exchange records | {Object} | [`dns.resolveMx()`][] |
294296
| `'NAPTR'` | name authority pointer records | {Object} | [`dns.resolveNaptr()`][] |
@@ -414,6 +416,22 @@ Uses the DNS protocol to resolve `CNAME` records for the `hostname`. The
414416
will contain an array of canonical name records available for the `hostname`
415417
(e.g. `['bar.example.com']`).
416418

419+
## `dns.resolveCaa(hostname, callback)`
420+
<!-- YAML
421+
added: REPLACEME
422+
-->
423+
424+
* `hostname` {string}
425+
* `callback` {Function}
426+
* `err` {Error}
427+
* `records` {Object[]}
428+
429+
Uses the DNS protocol to resolve `CAA` records for the `hostname`. The
430+
`addresses` argument passed to the `callback` function
431+
will contain an array of certification authority authorization records
432+
available for the `hostname` (e.g. `[{critial: 0, iodef:
433+
'mailto:pki@example.com'}, {critical: 128, issue: 'pki.example.com'}]`).
434+
417435
## `dns.resolveMx(hostname, callback)`
418436
<!-- YAML
419437
added: v0.1.27
@@ -665,6 +683,7 @@ The following methods from the `dnsPromises` API are available:
665683
* [`resolver.resolve4()`][`dnsPromises.resolve4()`]
666684
* [`resolver.resolve6()`][`dnsPromises.resolve6()`]
667685
* [`resolver.resolveAny()`][`dnsPromises.resolveAny()`]
686+
* [`resolver.resolveCaa()`][`dnsPromises.resolveCaa()`]
668687
* [`resolver.resolveCname()`][`dnsPromises.resolveCname()`]
669688
* [`resolver.resolveMx()`][`dnsPromises.resolveMx()`]
670689
* [`resolver.resolveNaptr()`][`dnsPromises.resolveNaptr()`]
@@ -806,6 +825,7 @@ based on `rrtype`:
806825
| `'A'` | IPv4 addresses (default) | {string} | [`dnsPromises.resolve4()`][] |
807826
| `'AAAA'` | IPv6 addresses | {string} | [`dnsPromises.resolve6()`][] |
808827
| `'ANY'` | any records | {Object} | [`dnsPromises.resolveAny()`][] |
828+
| `'CAA'` | CA authorization records | {Object} | [`dnsPromises.resolveCaa()`][] |
809829
| `'CNAME'` | canonical name records | {string} | [`dnsPromises.resolveCname()`][] |
810830
| `'MX'` | mail exchange records | {Object} | [`dnsPromises.resolveMx()`][] |
811831
| `'NAPTR'` | name authority pointer records | {Object} | [`dnsPromises.resolveNaptr()`][] |
@@ -895,6 +915,19 @@ Here is an example of the result object:
895915
minttl: 60 } ]
896916
```
897917

918+
## `dnsPromises.resolveCaa(hostname)`
919+
<!-- YAML
920+
added: REPLACEME
921+
-->
922+
923+
* `hostname` {string}
924+
925+
Uses the DNS protocol to resolve `CAA` records for the `hostname`. On success,
926+
the `Promise` is resolved with an array of objects containing available
927+
certification authority authorization records available for the `hostname`
928+
(e.g. `[{critial: 0, iodef: 'mailto:pki@example.com'},{critical: 128, issue:
929+
'pki.example.com'}]`).
930+
898931
### `dnsPromises.resolveCname(hostname)`
899932
<!-- YAML
900933
added: v10.6.0
@@ -1174,6 +1207,7 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
11741207
[`dns.resolve4()`]: #dns_dns_resolve4_hostname_options_callback
11751208
[`dns.resolve6()`]: #dns_dns_resolve6_hostname_options_callback
11761209
[`dns.resolveAny()`]: #dns_dns_resolveany_hostname_callback
1210+
[`dns.resolveCaa()`]: #dns_dns_resolvecaa_hostname_callback
11771211
[`dns.resolveCname()`]: #dns_dns_resolvecname_hostname_callback
11781212
[`dns.resolveMx()`]: #dns_dns_resolvemx_hostname_callback
11791213
[`dns.resolveNaptr()`]: #dns_dns_resolvenaptr_hostname_callback
@@ -1190,6 +1224,7 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
11901224
[`dnsPromises.resolve4()`]: #dns_dnspromises_resolve4_hostname_options
11911225
[`dnsPromises.resolve6()`]: #dns_dnspromises_resolve6_hostname_options
11921226
[`dnsPromises.resolveAny()`]: #dns_dnspromises_resolveany_hostname
1227+
[`dnsPromises.resolveCaa()`]: #dns_dnspromises_resolvecaa_hostname
11931228
[`dnsPromises.resolveCname()`]: #dns_dnspromises_resolvecname_hostname
11941229
[`dnsPromises.resolveMx()`]: #dns_dnspromises_resolvemx_hostname
11951230
[`dnsPromises.resolveNaptr()`]: #dns_dnspromises_resolvenaptr_hostname

0 commit comments

Comments
 (0)