Skip to content

Commit fd08c37

Browse files
lxdictedtargos
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: #35466Fixes: #19239 Refs: #14713 Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent bacd432 commit fd08c37

17 files changed

Lines changed: 421 additions & 1 deletion

File tree

‎deps/cares/cares.gyp‎

Lines changed: 1 addition & 0 deletions
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‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,15 @@ struct ares_addr6ttl {
528528
intttl;
529529
};
530530

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

649+
CARES_EXTERNintares_parse_caa_reply(constunsigned char*abuf,
650+
intalen,
651+
structares_caa_reply**caa_out);
652+
640653
CARES_EXTERNintares_parse_ptr_reply(constunsigned char*abuf,
641654
intalen,
642655
constvoid*addr,

‎deps/cares/include/nameser.h‎

Lines changed: 2 additions & 0 deletions
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
#defineT_AXFR ns_t_axfr
205206
#defineT_MAILB ns_t_mailb
206207
#defineT_MAILA ns_t_maila
208+
#defineT_CAA ns_t_caa
207209
#defineT_ANY ns_t_any
208210

209211
#endif/* HAVE_ARPA_NAMESER_COMPAT_H */

‎deps/cares/src/ares_data.c‎

Lines changed: 18 additions & 0 deletions
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+
caseARES_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+
caseARES_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
caseARES_DATATYPE_ADDR_NODE:
178196
ptr->data.addr_node.next=NULL;
179197
ptr->data.addr_node.family=0;

‎deps/cares/src/ares_data.h‎

Lines changed: 2 additions & 0 deletions
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
structares_mx_replymx_reply;
6667
structares_naptr_replynaptr_reply;
6768
structares_soa_replysoa_reply;
69+
structares_caa_replycaa_reply;
6870
} data;
6971
};
7072

Lines changed: 209 additions & 0 deletions
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+
#ifdefHAVE_NETINET_IN_H
20+
# include<netinet/in.h>
21+
#endif
22+
#ifdefHAVE_NETDB_H
23+
# include<netdb.h>
24+
#endif
25+
#ifdefHAVE_ARPA_INET_H
26+
# include<arpa/inet.h>
27+
#endif
28+
#ifdefHAVE_ARPA_NAMESER_H
29+
# include<arpa/nameser.h>
30+
#else
31+
# include"nameser.h"
32+
#endif
33+
#ifdefHAVE_ARPA_NAMESER_COMPAT_H
34+
# include<arpa/nameser_compat.h>
35+
#endif
36+
37+
#ifdefHAVE_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+
#ifndefT_CAA
47+
# defineT_CAA 257 /* Certification Authority Authorization */
48+
#endif
49+
50+
int
51+
ares_parse_caa_reply (constunsigned char*abuf, intalen,
52+
structares_caa_reply**caa_out)
53+
{
54+
unsigned intqdcount, ancount, i;
55+
constunsigned char*aptr;
56+
constunsigned char*strptr;
57+
intstatus, rr_type, rr_class, rr_len;
58+
longlen;
59+
char*hostname=NULL, *rr_name=NULL;
60+
structares_caa_reply*caa_head=NULL;
61+
structares_caa_reply*caa_last=NULL;
62+
structares_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+
returnARES_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+
returnARES_EBADRESP;
76+
if (ancount==0)
77+
returnARES_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+
returnstatus;
84+
85+
if (aptr+len+QFIXEDSZ>abuf+alen)
86+
{
87+
ares_free (hostname);
88+
returnARES_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+
returnstatus;
203+
}
204+
205+
/* everything looks fine, return the data */
206+
*caa_out=caa_head;
207+
208+
returnARES_SUCCESS;
209+
}

‎doc/api/dns.md‎

Lines changed: 35 additions & 0 deletions
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()`]
@@ -308,6 +309,7 @@ records. The type and structure of individual results varies based on `rrtype`:
308309
|`'A'`| IPv4 addresses (default) | {string} |[`dns.resolve4()`][]|
309310
|`'AAAA'`| IPv6 addresses | {string} |[`dns.resolve6()`][]|
310311
|`'ANY'`| any records | {Object} |[`dns.resolveAny()`][]|
312+
|`'CAA'`| CA authorization records | {Object} |[`dns.resolveCaa()`][]|
311313
|`'CNAME'`| canonical name records | {string} |[`dns.resolveCname()`][]|
312314
|`'MX'`| mail exchange records | {Object} |[`dns.resolveMx()`][]|
313315
|`'NAPTR'`| name authority pointer records | {Object} |[`dns.resolveNaptr()`][]|
@@ -433,6 +435,22 @@ Uses the DNS protocol to resolve `CNAME` records for the `hostname`. The
433435
will contain an array of canonical name records available for the `hostname`
434436
(e.g. `['bar.example.com']`).
435437

438+
## `dns.resolveCaa(hostname, callback)`
439+
<!-- YAML
440+
added: REPLACEME
441+
-->
442+
443+
*`hostname` {string}
444+
*`callback` {Function}
445+
*`err` {Error}
446+
*`records` {Object[]}
447+
448+
Uses the DNS protocol to resolve `CAA` records for the `hostname`. The
449+
`addresses` argument passed to the `callback` function
450+
will contain an array of certification authority authorization records
451+
available for the `hostname` (e.g. `[{critial: 0, iodef:
452+
'mailto:pki@example.com'}, {critical: 128, issue: 'pki.example.com'}]`).
453+
436454
## `dns.resolveMx(hostname, callback)`
437455
<!-- YAML
438456
added: v0.1.27
@@ -686,6 +704,7 @@ The following methods from the `dnsPromises` API are available:
686704
*[`resolver.resolve4()`][`dnsPromises.resolve4()`]
687705
*[`resolver.resolve6()`][`dnsPromises.resolve6()`]
688706
*[`resolver.resolveAny()`][`dnsPromises.resolveAny()`]
707+
*[`resolver.resolveCaa()`][`dnsPromises.resolveCaa()`]
689708
*[`resolver.resolveCname()`][`dnsPromises.resolveCname()`]
690709
*[`resolver.resolveMx()`][`dnsPromises.resolveMx()`]
691710
*[`resolver.resolveNaptr()`][`dnsPromises.resolveNaptr()`]
@@ -827,6 +846,7 @@ based on `rrtype`:
827846
|`'A'`| IPv4 addresses (default) | {string} |[`dnsPromises.resolve4()`][]|
828847
|`'AAAA'`| IPv6 addresses | {string} |[`dnsPromises.resolve6()`][]|
829848
|`'ANY'`| any records | {Object} |[`dnsPromises.resolveAny()`][]|
849+
|`'CAA'`| CA authorization records | {Object} |[`dnsPromises.resolveCaa()`][]|
830850
|`'CNAME'`| canonical name records | {string} |[`dnsPromises.resolveCname()`][]|
831851
|`'MX'`| mail exchange records | {Object} |[`dnsPromises.resolveMx()`][]|
832852
|`'NAPTR'`| name authority pointer records | {Object} |[`dnsPromises.resolveNaptr()`][]|
@@ -916,6 +936,19 @@ Here is an example of the result object:
916936
minttl:60 } ]
917937
```
918938

939+
## `dnsPromises.resolveCaa(hostname)`
940+
<!-- YAML
941+
added: REPLACEME
942+
-->
943+
944+
*`hostname` {string}
945+
946+
Uses the DNS protocol to resolve `CAA` records for the `hostname`. On success,
947+
the `Promise` is resolved with an array of objects containing available
948+
certification authority authorization records available for the `hostname`
949+
(e.g. `[{critial: 0, iodef: 'mailto:pki@example.com'},{critical: 128, issue:
950+
'pki.example.com'}]`).
951+
919952
### `dnsPromises.resolveCname(hostname)`
920953
<!-- YAML
921954
added: v10.6.0
@@ -1195,6 +1228,7 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
11951228
[`dns.resolve4()`]: #dns_dns_resolve4_hostname_options_callback
11961229
[`dns.resolve6()`]: #dns_dns_resolve6_hostname_options_callback
11971230
[`dns.resolveAny()`]: #dns_dns_resolveany_hostname_callback
1231+
[`dns.resolveCaa()`]: #dns_dns_resolvecaa_hostname_callback
11981232
[`dns.resolveCname()`]: #dns_dns_resolvecname_hostname_callback
11991233
[`dns.resolveMx()`]: #dns_dns_resolvemx_hostname_callback
12001234
[`dns.resolveNaptr()`]: #dns_dns_resolvenaptr_hostname_callback
@@ -1211,6 +1245,7 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
12111245
[`dnsPromises.resolve4()`]: #dns_dnspromises_resolve4_hostname_options
12121246
[`dnsPromises.resolve6()`]: #dns_dnspromises_resolve6_hostname_options
12131247
[`dnsPromises.resolveAny()`]: #dns_dnspromises_resolveany_hostname
1248+
[`dnsPromises.resolveCaa()`]: #dns_dnspromises_resolvecaa_hostname
12141249
[`dnsPromises.resolveCname()`]: #dns_dnspromises_resolvecname_hostname
12151250
[`dnsPromises.resolveMx()`]: #dns_dnspromises_resolvemx_hostname
12161251
[`dnsPromises.resolveNaptr()`]: #dns_dnspromises_resolvenaptr_hostname

0 commit comments

Comments
 (0)