Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added support for resolving DNS CAA records
This adds support for DNS Certification Authority
Authorization (RFC 6844) to nodejs.

This closes #19239 and possibly affects #14713.
  • Loading branch information
lxdicted committed Oct 2, 2020
commit 08d2e88c0afd301bf120d048452098552f0b1888
17 changes: 17 additions & 0 deletions doc/api/dns.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ The following methods from the `dns` module are available:
* [`resolver.resolve4()`][`dns.resolve4()`]
* [`resolver.resolve6()`][`dns.resolve6()`]
* [`resolver.resolveAny()`][`dns.resolveAny()`]
* [`resolver.resolveCaa()`][`dns.resolveCaa()`]
* [`resolver.resolveCname()`][`dns.resolveCname()`]
* [`resolver.resolveMx()`][`dns.resolveMx()`]
* [`resolver.resolveNaptr()`][`dns.resolveNaptr()`]
Expand Down Expand Up @@ -290,6 +291,7 @@ records. The type and structure of individual results varies based on `rrtype`:
| `'AAAA'` | IPv6 addresses | {string} | [`dns.resolve6()`][] |
| `'ANY'` | any records | {Object} | [`dns.resolveAny()`][] |
| `'CNAME'` | canonical name records | {string} | [`dns.resolveCname()`][] |
| `'CAA'` | CA authorization | {Object} | [`dns.resolveCaa()`][] |
| `'MX'` | mail exchange records | {Object} | [`dns.resolveMx()`][] |
| `'NAPTR'` | name authority pointer records | {Object} | [`dns.resolveNaptr()`][] |
| `'NS'` | name server records | {string} | [`dns.resolveNs()`][] |
Expand Down Expand Up @@ -414,6 +416,21 @@ Uses the DNS protocol to resolve `CNAME` records for the `hostname`. The
will contain an array of canonical name records available for the `hostname`
(e.g. `['bar.example.com']`).

## `dns.resolveCaa(hostname, callback)`
<!-- YAML
added: v0.3.2
Comment thread
lxdicted marked this conversation as resolved.
Resolved
-->

* `hostname` {string}
* `callback` {Function}
* `err` {Error}
* `records` {object[]}

Uses the DNS protocol to resolve `CAA` records for the `hostname`. The
`addresses` argument passed to the `callback` function
will contain an array of certification authority authorization records
available for the `hostname` (e.g. `[{critial: 0, iodef: 'letsencrypt.org']`).

## `dns.resolveMx(hostname, callback)`
<!-- YAML
added: v0.1.27
Expand Down
1 change: 1 addition & 0 deletions lib/dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ const resolveMap = ObjectCreate(null);
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
Resolver.prototype.resolveCaa = resolveMap.CAA = resolver('queryCaa');
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
Expand Down
1 change: 1 addition & 0 deletions lib/internal/dns/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ Resolver.prototype.setServers = CallbackResolver.prototype.setServers;
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
Resolver.prototype.resolveCaa = resolveMap.CAA = resolver('queryCaa');
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
Expand Down
1 change: 1 addition & 0 deletions lib/internal/dns/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ const resolverKeys = [
'resolve4',
'resolve6',
'resolveAny',
'resolveCaa',
'resolveCname',
'resolveMx',
'resolveNaptr',
Expand Down
68 changes: 68 additions & 0 deletions src/cares_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,43 @@ int ParseMxReply(Environment* env,
return ARES_SUCCESS;
}

int ParseCaaReply(Environment* env,
const unsigned char* buf,
int len,
Local<Array> ret,
bool need_type = false) {
HandleScope handle_scope(env->isolate());
auto context = env->context();

struct ares_caa_reply* caa_start;
int status = ares_parse_caa_reply(buf, len, &caa_start);
if (status != ARES_SUCCESS) {
return status;
}

uint32_t offset = ret->Length();
ares_caa_reply* current = caa_start;
for (uint32_t i = 0; current != nullptr; ++i, current = current->next) {
Local<Object> caa_record = Object::New(env->isolate());

caa_record->Set(context,
env->dns_critical_string(),
Integer::New(env->isolate(), current->critical)).Check();
caa_record->Set(context,
OneByteString(env->isolate(), current->property),
OneByteString(env->isolate(), current->value)).Check();
if (need_type)
caa_record->Set(context,
env->type_string(),
env->dns_caa_string()).Check();

ret->Set(context, i + offset, caa_record).Check();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you leave a todo comment to do proper exception handling in this method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no exception handling in cares_wrap.cc. If this was needed, it would have been added for all parsing the other record types, too. I don't see a todo here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no exception handling in cares_wrap.cc.

Right, it’s missing, so it would be nice to have a comment about that somewhere in the file

}

ares_free_data(caa_start);
return ARES_SUCCESS;
}

int ParseTxtReply(Environment* env,
const unsigned char* buf,
int len,
Expand Down Expand Up @@ -1445,6 +1482,36 @@ class QueryAaaaWrap: public QueryWrap {
}
};

class QueryCaaWrap: public QueryWrap {
public:
QueryCaaWrap(ChannelWrap* channel, Local<Object> req_wrap_obj)
: QueryWrap(channel, req_wrap_obj, "resolve6") {
}

int Send(const char* name) override {
AresQuery(name, ns_c_in, ns_t_caa);
return 0;
}

SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(QueryAaaaWrap)
SET_SELF_SIZE(QueryAaaaWrap)

protected:
void Parse(unsigned char* buf, int len) override {
HandleScope handle_scope(env()->isolate());
Context::Scope context_scope(env()->context());

Local<Array> ret = Array::New(env()->isolate());
int status = ParseCaaReply(env(), buf, len, ret);
if (status != ARES_SUCCESS) {
ParseError(status);
return;
}

this->CallOnComplete(ret);
}
};

class QueryCnameWrap: public QueryWrap {
public:
Expand Down Expand Up @@ -2242,6 +2309,7 @@ void Initialize(Local<Object> target,
env->SetProtoMethod(channel_wrap, "queryAny", Query<QueryAnyWrap>);
env->SetProtoMethod(channel_wrap, "queryA", Query<QueryAWrap>);
env->SetProtoMethod(channel_wrap, "queryAaaa", Query<QueryAaaaWrap>);
env->SetProtoMethod(channel_wrap, "queryCaa", Query<QueryCaaWrap>);
env->SetProtoMethod(channel_wrap, "queryCname", Query<QueryCnameWrap>);
env->SetProtoMethod(channel_wrap, "queryMx", Query<QueryMxWrap>);
env->SetProtoMethod(channel_wrap, "queryNs", Query<QueryNsWrap>);
Expand Down
2 changes: 2 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ constexpr size_t kFsStatsBufferLength =
V(dh_string, "DH") \
V(dns_a_string, "A") \
V(dns_aaaa_string, "AAAA") \
V(dns_caa_string, "CAA") \
V(dns_critical_string, "critical") \
V(dns_cname_string, "CNAME") \
V(dns_mx_string, "MX") \
V(dns_naptr_string, "NAPTR") \
Expand Down
2 changes: 2 additions & 0 deletions test/common/internet.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const addresses = {
NAPTR_HOST: 'sip2sip.info',
// A host with SOA records registered
SOA_HOST: 'nodejs.org',
// A host with CAA record registred
CAA_HOST: 'google.com',
// A host with CNAME records registered
CNAME_HOST: 'blog.nodejs.org',
// A host with NS records registered
Expand Down
41 changes: 41 additions & 0 deletions test/internet/test-dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,47 @@ TEST(function test_resolveSoa_failure(done) {
checkWrap(req);
});

TEST(async function test_resolveCaa(done) {
function validateResult(result) {
assert.ok(Array.isArray(result[0]));
assert.strictEqual(result.length, 1);
assert.strictEqual(typeof result[0].critical, 'number');
assert.strictEqual(result[0].critical, 0);
assert.strictEqual(result[0].issue, 'pki.goog');
}

validateResult(await dnsPromises.resolveCaa(addresses.CAA_HOST));

const req = dns.resolveCaa(addresses.CAA_HOST, function(err, records) {
assert.ifError(err);
validateResult(records);
done();
});

checkWrap(req);
});

TEST(function test_resolveCaa_failure(done) {
dnsPromises.resolveTxt(addresses.INVALID_HOST)
.then(common.mustNotCall())
.catch(common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOTFOUND');
}));

const req = dns.resolveCaa(addresses.INVALID_HOST, function(err, result) {
assert.ok(err instanceof Error);
assert.strictEqual(err.code, 'ENOTFOUND');

assert.strictEqual(result, undefined);

done();
});

checkWrap(req);
});
Comment thread
addaleax marked this conversation as resolved.
Resolved



TEST(async function test_resolveCname(done) {
function validateResult(result) {
assert.ok(result.length > 0);
Expand Down
1 change: 1 addition & 0 deletions test/internet/test-trace-events-dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const tests = {
'resolveAny': 'dns.resolveAny("example.com", (err, res) => {});',
'resolve4': 'dns.resolve4("example.com", (err, res) => {});',
'resolve6': 'dns.resolve6("example.com", (err, res) => {});',
'resolveCaa': 'dns.resolveCaa("example.com", (err, res) => {});',
'resolveCname': 'dns.resolveCname("example.com", (err, res) => {});',
'resolveMx': 'dns.resolveMx("example.com", (err, res) => {});',
'resolveNs': 'dns.resolveNs("example.com", (err, res) => {});',
Expand Down