Discussion:
[Dnsmasq-discuss] [PATCH] Treat records signed using unknown algorithms as unsigned instead of bogus
Michał Kępień
2015-11-17 11:01:04 UTC
Permalink
---
When dnsmasq is running with DNSSEC validation enabled, it returns
SERVFAIL when trying to resolve any record within a zone which uses a
signing algorithm it doesn't understand. This behavior doesn't play
nicely with RFC 4035, section 5.2:

If the resolver does not support any of the algorithms listed in an
authenticated DS RRset, then the resolver will not be able to verify
the authentication path to the child zone. In this case, the
resolver SHOULD treat the child zone as if it were unsigned.

My reading of the above is that e.g. if a resolver encounters a zone for
which there is a single DS with a signing/hashing algorithm it doesn't
understand, it should treat that zone as unsigned, not bogus.

I would venture to say that this rule should not be applied solely to DS
records, but also to record signatures in general. A quick glance at
the latest src/dnssec.c shows that both validate_rrset() and
dnssec_validate_by_ds() return STAT_BOGUS when they are unable to
perform validation, no matter the reason. Meanwhile, if the hash_find()
call fails for all DS records for a given zone (or for all RRSIGs for a
given RRset), it simply means that dnsmasq doesn't know how to perform
validation, not that the data is outright bogus. The same holds true
for verify() which simply returns 0 when either signature verification
fails or the signature algorithm is not supported, which are two
distinct cases.

This patch attempts to solve the issue by tracking how many of the
attempted validations failed due to unknown algorithms being used. It
probably needs some polishing and more thorough testing, but hopefully
conveys the idea.

src/dnssec.c | 64 ++++++++++++++++++++++++++++++++++++++++++------------------
1 file changed, 45 insertions(+), 19 deletions(-)

diff --git a/src/dnssec.c b/src/dnssec.c
index 67ce486..aa22e4e 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -291,7 +291,7 @@ static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len
#endif

static int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
- unsigned char *digest, size_t digest_len, int algo)
+ unsigned char *digest, size_t digest_len, int algo, int *val_unable)
{
(void)digest_len;

@@ -309,6 +309,7 @@ static int verify(struct blockdata *key_data, unsigned int key_len, unsigned cha
#endif
}

+ (*val_unable)++;
return 0;
}

@@ -712,7 +713,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
(In this case *wildcard_out points to the "body" of the wildcard within name.)
STAT_NO_SIG no RRsigs found.
- STAT_INSECURE RRset empty.
+ STAT_INSECURE RRset empty or all used signature algorithms are unknown
STAT_BOGUS signature is wrong, bad packet.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)

@@ -728,7 +729,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
static int rrset_sz = 0, sig_sz = 0;

unsigned char *p;
- int rrsetidx, sigidx, res, rdlen, j, name_labels;
+ int rrsetidx, sigidx, res, rdlen, j, name_labels, val_attempts = 0, val_unable = 0;
struct crec *crecp = NULL;
int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag;
u16 *rr_desc = get_desc(type);
@@ -820,6 +821,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
char *name_start;
u32 nsigttl;

+ val_attempts++;
+
p = sigs[j];
GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */
psav = p;
@@ -861,11 +864,16 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in

/* Other 5.3.1 checks */
if (!check_date_range(sig_inception, sig_expiration) ||
- labels > name_labels ||
- !(hash = hash_find(algo_digest_name(algo))) ||
- !hash_init(hash, &ctx, &digest))
+ labels > name_labels)
continue;
-
+
+ if (!(hash = hash_find(algo_digest_name(algo))) ||
+ !hash_init(hash, &ctx, &digest))
+ {
+ val_unable++;
+ continue;
+ }
+
/* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
return STAT_NEED_KEY;
@@ -950,7 +958,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (key)
{
if (algo_in == algo && keytag_in == key_tag &&
- verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
+ verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo, &val_unable))
return STAT_SECURE;
}
else
@@ -960,18 +968,20 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (crecp->addr.key.algo == algo &&
crecp->addr.key.keytag == key_tag &&
crecp->uid == (unsigned int)class &&
- verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
+ verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo, &val_unable))
return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
}
}

- return STAT_BOGUS;
+ return val_attempts == val_unable ? STAT_INSECURE : STAT_BOGUS;
}

/* The DNS packet is expected to contain the answer to a DNSKEY query.
Put all DNSKEYs in the answer which are valid into the cache.
return codes:
STAT_SECURE At least one valid DNSKEY found and in cache.
+ STAT_INSECURE DNSKEY RRset could not be validated because all
+ DS/RRSIG records are using unknown algorithms
STAT_BOGUS No DNSKEYs found, which can be validated with DS,
or self-sign for DNSKEY RRset is not valid, bad packet.
STAT_NEED_DS DS records to validate a key not found, name in keyname
@@ -980,7 +990,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
{
unsigned char *psave, *p = (unsigned char *)(header+1);
struct crec *crecp, *recp1;
- int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag, type_covered;
+ int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag, type_covered, val_attempts = 0, val_unable = 0;
struct blockdata *key;
struct all_addr a;

@@ -1060,11 +1070,18 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch

if (recp1->addr.ds.algo == algo &&
recp1->addr.ds.keytag == keytag &&
- recp1->uid == (unsigned int)class &&
- (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) &&
- hash_init(hash, &ctx, &digest))
-
+ recp1->uid == (unsigned int)class)
{
+
+ val_attempts++;
+
+ if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) ||
+ !hash_init(hash, &ctx, &digest))
+ {
+ val_unable++;
+ continue;
+ }
+
int wire_len = to_wire(name);

/* Note that digest may be different between DSs, so
@@ -1077,11 +1094,18 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch

if (recp1->addr.ds.keylen == (int)hash->digest_size &&
(ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&
- memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
- validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
+ memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0)
{
- valid = 1;
- break;
+ rc = validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag);
+ if (rc == STAT_SECURE)
+ {
+ valid = 1;
+ break;
+ }
+ else if (rc == STAT_INSECURE)
+ {
+ val_unable++;
+ }
}
}
}
@@ -1186,6 +1210,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
/* commit cache insert. */
cache_end_insert();
return STAT_SECURE;
+ } else if (val_attempts == val_unable) {
+ return STAT_INSECURE;
}

log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
--
2.6.2
Simon Kelley
2015-11-19 14:17:27 UTC
Permalink
My first thought on reading this is that it's a big hole. All an
attacker needs to do is supply a false DS with nonsense algorithm
numbers to turn off validation. Then I thought again, and realised
that's not actually true, as long as the DS records validate. If the
DS RRset has a valid signature, but no algorithm we can use, then it's
fine to treat it the same as an NSEC/NSEC3 asserting there's no DS
record. There are a few wrinkles in doing that, so I need to find some
time to think through the patch, but the principle looks like a good
one, and this, or something like it, will get applied.


Thanks for your efforts.


Cheers,

Simon.
--- When dnsmasq is running with DNSSEC validation enabled, it
returns SERVFAIL when trying to resolve any record within a zone
which uses a signing algorithm it doesn't understand. This
If the resolver does not support any of the algorithms listed in
an authenticated DS RRset, then the resolver will not be able to
verify the authentication path to the child zone. In this case,
the resolver SHOULD treat the child zone as if it were unsigned.
My reading of the above is that e.g. if a resolver encounters a
zone for which there is a single DS with a signing/hashing
algorithm it doesn't understand, it should treat that zone as
unsigned, not bogus.
I would venture to say that this rule should not be applied solely
to DS records, but also to record signatures in general. A quick
glance at the latest src/dnssec.c shows that both validate_rrset()
and dnssec_validate_by_ds() return STAT_BOGUS when they are unable
to perform validation, no matter the reason. Meanwhile, if the
hash_find() call fails for all DS records for a given zone (or for
all RRSIGs for a given RRset), it simply means that dnsmasq doesn't
know how to perform validation, not that the data is outright
bogus. The same holds true for verify() which simply returns 0
when either signature verification fails or the signature algorithm
is not supported, which are two distinct cases.
This patch attempts to solve the issue by tracking how many of the
attempted validations failed due to unknown algorithms being used.
It probably needs some polishing and more thorough testing, but
hopefully conveys the idea.
src/dnssec.c | 64
++++++++++++++++++++++++++++++++++++++++++------------------ 1 file
changed, 45 insertions(+), 19 deletions(-)
diff --git a/src/dnssec.c b/src/dnssec.c index 67ce486..aa22e4e
static int dnsmasq_ecdsa_verify(struct blockdata *key_data,
unsigned int key_len #endif
static int verify(struct blockdata *key_data, unsigned int key_len,
unsigned char *sig, size_t sig_len, - unsigned char *digest,
size_t digest_len, int algo) + unsigned char *digest, size_t
digest_len, int algo, int *val_unable) { (void)digest_len;
@@ -309,6 +309,7 @@ static int verify(struct blockdata *key_data,
unsigned int key_len, unsigned cha #endif }
+ (*val_unable)++; return 0; }
@@ -712,7 +713,7 @@ static void sort_rrset(struct dns_header
*header, size_t plen, u16 *rr_desc, int STAT_SECURE_WILDCARD if it
validates and is the result of wildcard expansion. (In this case
*wildcard_out points to the "body" of the wildcard within name.)
STAT_NO_SIG no RRsigs found. - STAT_INSECURE RRset empty. +
STAT_INSECURE RRset empty or all used signature algorithms are
unknown STAT_BOGUS signature is wrong, bad packet. STAT_NEED_KEY
need DNSKEY to complete validation (name is returned in keyname)
@@ -728,7 +729,7 @@ static int validate_rrset(time_t now, struct
dns_header *header, size_t plen, in static int rrset_sz = 0, sig_sz
= 0;
unsigned char *p; - int rrsetidx, sigidx, res, rdlen, j,
name_labels; + int rrsetidx, sigidx, res, rdlen, j, name_labels,
val_attempts = 0, val_unable = 0; struct crec *crecp = NULL; int
type_covered, algo, labels, orig_ttl, sig_expiration,
*header, size_t plen, in char *name_start; u32 nsigttl;
+ val_attempts++; + p = sigs[j]; GETSHORT(rdlen, p); /* rdlen
int validate_rrset(time_t now, struct dns_header *header, size_t
plen, in
/* Other 5.3.1 checks */ if (!check_date_range(sig_inception,
sig_expiration) || - labels > name_labels || - !(hash =
hash_find(algo_digest_name(algo))) || - !hash_init(hash, &ctx,
&digest)) + labels > name_labels) continue; - + + if (!(hash
= hash_find(algo_digest_name(algo))) || + !hash_init(hash, &ctx,
&digest)) + { + val_unable++; + continue; + } + /* OK, we have
the signature record, see if the relevant DNSKEY is in the cache.
*/ if (!key && !(crecp = cache_find_by_name(NULL, keyname, now,
validate_rrset(time_t now, struct dns_header *header, size_t plen,
in if (key) { if (algo_in == algo && keytag_in == key_tag && -
verify(key, keylen, sig, sig_len, digest, hash->digest_size,
algo)) + verify(key, keylen, sig, sig_len, digest,
hash->digest_size, algo, &val_unable)) return STAT_SECURE; } else
@@ -960,18 +968,20 @@ static int validate_rrset(time_t now, struct
dns_header *header, size_t plen, in if (crecp->addr.key.algo ==
algo && crecp->addr.key.keytag == key_tag && crecp->uid ==
(unsigned int)class && - verify(crecp->addr.key.keydata,
crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size,
algo)) + verify(crecp->addr.key.keydata, crecp->addr.key.keylen,
sig, sig_len, digest, hash->digest_size, algo, &val_unable)) return
(labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE; } }
- return STAT_BOGUS; + return val_attempts == val_unable ?
STAT_INSECURE : STAT_BOGUS; }
/* The DNS packet is expected to contain the answer to a DNSKEY
query. Put all DNSKEYs in the answer which are valid into the
cache. return codes: STAT_SECURE At least one valid DNSKEY found
and in cache. + STAT_INSECURE DNSKEY RRset could not be validated
because all + DS/RRSIG records are using unknown
algorithms STAT_BOGUS No DNSKEYs found, which can be validated
with DS, or self-sign for DNSKEY RRset is not valid, bad packet.
STAT_NEED_DS DS records to validate a key not found, name in
struct dns_header *header, size_t plen, ch { unsigned char *psave,
*p = (unsigned char *)(header+1); struct crec *crecp, *recp1; -
int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag,
type_covered; + int rc, j, qtype, qclass, ttl, rdlen, flags, algo,
valid, keytag, type_covered, val_attempts = 0, val_unable = 0;
struct blockdata *key; struct all_addr a;
@@ -1060,11 +1070,18 @@ int dnssec_validate_by_ds(time_t now,
struct dns_header *header, size_t plen, ch if (recp1->addr.ds.algo
== algo && recp1->addr.ds.keytag == keytag && - recp1->uid ==
(unsigned int)class && - (hash =
hash_find(ds_digest_name(recp1->addr.ds.digest))) && -
hash_init(hash, &ctx, &digest)) - + recp1->uid == (unsigned
int)class) { + + val_attempts++; + + if (!(hash =
hash_find(ds_digest_name(recp1->addr.ds.digest))) || +
!hash_init(hash, &ctx, &digest)) + { + val_unable++; +
continue; + } + int wire_len = to_wire(name); /* Note that
dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t
plen, ch if (recp1->addr.ds.keylen == (int)hash->digest_size &&
(ds_digest = blockdata_retrieve(recp1->addr.key.keydata,
recp1->addr.ds.keylen, NULL)) && - memcmp(ds_digest, digest,
recp1->addr.ds.keylen) == 0 && - validate_rrset(now, header,
plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo,
keytag) == STAT_SECURE) + memcmp(ds_digest, digest,
recp1->addr.ds.keylen) == 0) { - valid = 1; - break; + rc
= validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname,
NULL, key, rdlen - 4, algo, keytag); + if (rc == STAT_SECURE) +
{ + valid = 1; + break; + } + else if (rc ==
dns_header *header, size_t plen, ch /* commit cache insert. */
cache_end_insert(); return STAT_SECURE; + } else if
(val_attempts == val_unable) { + return STAT_INSECURE; }
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
Simon Kelley
2015-11-20 23:26:26 UTC
Permalink
Post by Michał Kępień
---
When dnsmasq is running with DNSSEC validation enabled, it returns
SERVFAIL when trying to resolve any record within a zone which uses a
signing algorithm it doesn't understand. This behavior doesn't play
If the resolver does not support any of the algorithms listed in an
authenticated DS RRset, then the resolver will not be able to verify
the authentication path to the child zone. In this case, the
resolver SHOULD treat the child zone as if it were unsigned.
My reading of the above is that e.g. if a resolver encounters a zone for
which there is a single DS with a signing/hashing algorithm it doesn't
understand, it should treat that zone as unsigned, not bogus.
I would venture to say that this rule should not be applied solely to DS
records, but also to record signatures in general. A quick glance at
the latest src/dnssec.c shows that both validate_rrset() and
dnssec_validate_by_ds() return STAT_BOGUS when they are unable to
perform validation, no matter the reason. Meanwhile, if the hash_find()
call fails for all DS records for a given zone (or for all RRSIGs for a
given RRset), it simply means that dnsmasq doesn't know how to perform
validation, not that the data is outright bogus. The same holds true
for verify() which simply returns 0 when either signature verification
fails or the signature algorithm is not supported, which are two
distinct cases.
This patch attempts to solve the issue by tracking how many of the
attempted validations failed due to unknown algorithms being used. It
probably needs some polishing and more thorough testing, but hopefully
conveys the idea.
OK, I've done some more thinking about this. We have to be careful to
distinguish between validating a DS RRset and using that DS RRset to
prove that the DNSKEY RRset it refers to is valid. If we can't validate
a DS RRset, either because its signature is wrong, or we don't speak
the correct algorithm, then we can't use it to prove anything about the
zone it refers to. I'll return to that case later.

There's another case where the DS RRset is validated - we know it's good
data, but we don't speak the hash algorithm is gives. In that case, we
can treat the zone as insecure, in exactly the same way as if we have an
NSEC record proving that the DS doesn't exist. 4035 says as much.


If the validator does not support any of the algorithms listed in an
authenticated DS RRset, then the resolver has no supported
authentication path leading from the parent to the child. The
resolver should treat this case as it would the case of an
authenticated NSEC RRset proving that no DS RRset exists, as
described above.

(An interesting bit of RFC-ology this, my RFC quote is two paragraphs
ahead of yours: they both refer to the same state, and say subtly
different things.)

This isn't what dnsmasq does at present, and I'll fix that, but I doubt
it's the problem you've been seeing, since it's about hash algorithms.
AFAIK there are no new hash algorithms out there. More likely the
problem is that a zone is _signed_ with a new public-key algorithm
(probably ECDSA) The following bit of 4035 might indicate that treating
such data as unisgned is not correct.


If for whatever reason none of the RRSIGs can be validated, the
response SHOULD be considered BAD. If the validation was being done
to service a recursive query, the name server MUST return RCODE 2 to
the originating client. However, it MUST return the full response if
and only if the original query had the CD bit set. Also see Section
4.7 on caching responses that do not validate.


PS. Change for DS unknown hash is:

http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=67ab3285b5d9a1b1e20e034cf272867fdab8a0f9


Cheers,

Simon.
Michał Kępień
2015-11-23 13:21:21 UTC
Permalink
Post by Simon Kelley
OK, I've done some more thinking about this. We have to be careful to
distinguish between validating a DS RRset and using that DS RRset to
prove that the DNSKEY RRset it refers to is valid. If we can't validate
a DS RRset, either because its signature is wrong, or we don't speak
the correct algorithm, then we can't use it to prove anything about the
zone it refers to. I'll return to that case later.
There's another case where the DS RRset is validated - we know it's good
data, but we don't speak the hash algorithm is gives. In that case, we
can treat the zone as insecure, in exactly the same way as if we have an
NSEC record proving that the DS doesn't exist. 4035 says as much.
If the validator does not support any of the algorithms listed in an
authenticated DS RRset, then the resolver has no supported
authentication path leading from the parent to the child. The
resolver should treat this case as it would the case of an
authenticated NSEC RRset proving that no DS RRset exists, as
described above.
(An interesting bit of RFC-ology this, my RFC quote is two paragraphs
ahead of yours: they both refer to the same state, and say subtly
different things.)
This isn't what dnsmasq does at present, and I'll fix that, but I doubt
it's the problem you've been seeing, since it's about hash algorithms.
Could you please explain why do you think that the above excerpt from
RFC 4035 only applies to hash algorithms and not signing algorithms as
well? As far as I can tell, if we're discussing a zone signed using
exclusively algorithms the resolver doesn't understand, with a properly
validated DS in its parent zone, in the end there is no difference
between "I don't know how to calculate a digest of any of these DNSKEYs"
(unknown hashing algorithm) vs. "I don't know how to verify any of this
DNSKEY RRset's signatures" (unknown signing algorithm), because both of
these cases boil down to "I'm unable to prove this DNSKEY RRset is
secure, but I'm unable to prove it is bogus either".
Post by Simon Kelley
AFAIK there are no new hash algorithms out there. More likely the
problem is that a zone is _signed_ with a new public-key algorithm
(probably ECDSA) The following bit of 4035 might indicate that treating
such data as unisgned is not correct.
If for whatever reason none of the RRSIGs can be validated, the
response SHOULD be considered BAD. If the validation was being done
to service a recursive query, the name server MUST return RCODE 2 to
the originating client. However, it MUST return the full response if
and only if the original query had the CD bit set. Also see Section
4.7 on caching responses that do not validate.
Yes, the section you quoted is in there indeed, but its header reads
"Resolver Behavior When Signatures Do Not Validate". I believe there
may be a subtle difference between "signatures do not validate" and
"signatures cannot be validated".

I am not claiming my interpretation is the only valid one. While
researching this topic, I came across RFC 4955, which discusses the
methodology for setting up DNSSEC experiments which uses "strictly
unknown algorithm identifiers when signing the experimental zone, and
more importantly, having only unknown algorithm identifiers in the DS
records for the delegation to the zone at the parent". Section 4
contains the following quote discussing treating the zone as unsigned
when a DS with only unknown algorithm identifiers is encountered:

Although this behavior isn't strictly mandatory (as marked by MUST),
it is unlikely for a validator to implement a substantially
different behavior. Essentially, if the validator does not have a
usable chain of trust to a child zone, then it can only do one of
two things: treat responses from the zone as insecure (the
recommended behavior), or treat the responses as bogus. If the
validator chooses the latter, this will both violate the expectation
of the zone owner and defeat the purpose of the above rule.
However, with local policy, it is within the right of a validator to
refuse to trust certain zones based on any criteria, including the
use of unknown signing algorithms.

I haven't yet found another validating resolver in the wild that would
return SERVFAILs for zones signed using unknown algorithms, which is why
I'm so curious about your reasons for implementing it this way.
Post by Simon Kelley
http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=67ab3285b5d9a1b1e20e034cf272867fdab8a0f9
Thanks!
--
Best regards,
Michał Kępień
Simon Kelley
2015-11-23 23:00:47 UTC
Permalink
Post by Michał Kępień
Post by Simon Kelley
OK, I've done some more thinking about this. We have to be careful to
distinguish between validating a DS RRset and using that DS RRset to
prove that the DNSKEY RRset it refers to is valid. If we can't validate
a DS RRset, either because its signature is wrong, or we don't speak
the correct algorithm, then we can't use it to prove anything about the
zone it refers to. I'll return to that case later.
There's another case where the DS RRset is validated - we know it's good
data, but we don't speak the hash algorithm is gives. In that case, we
can treat the zone as insecure, in exactly the same way as if we have an
NSEC record proving that the DS doesn't exist. 4035 says as much.
If the validator does not support any of the algorithms listed in an
authenticated DS RRset, then the resolver has no supported
authentication path leading from the parent to the child. The
resolver should treat this case as it would the case of an
authenticated NSEC RRset proving that no DS RRset exists, as
described above.
(An interesting bit of RFC-ology this, my RFC quote is two paragraphs
ahead of yours: they both refer to the same state, and say subtly
different things.)
This isn't what dnsmasq does at present, and I'll fix that, but I doubt
it's the problem you've been seeing, since it's about hash algorithms.
Could you please explain why do you think that the above excerpt from
RFC 4035 only applies to hash algorithms and not signing algorithms as
well? As far as I can tell, if we're discussing a zone signed using
exclusively algorithms the resolver doesn't understand, with a properly
validated DS in its parent zone, in the end there is no difference
between "I don't know how to calculate a digest of any of these DNSKEYs"
(unknown hashing algorithm) vs. "I don't know how to verify any of this
DNSKEY RRset's signatures" (unknown signing algorithm), because both of
these cases boil down to "I'm unable to prove this DNSKEY RRset is
secure, but I'm unable to prove it is bogus either".
Caveat. I'm not sure what the answer is. I'm certainly not arguing for a
fixed interpretation, not even the current behaviour of dnsmasq, and I'm
trying to understand what the correct behaviour should be. As always,
I'm terrified of breaking people's DNS by rejecting something that's OK,
and I'm also terrified or creating a security hole by accepting a answer
that's actually an attack which should be rejected.

The difference between unknown DS hashes, and unknown signature is as
follows, as best I understand it. In the DS case, we have a DS RRset
which is signed, and validated. We know it has been signed, and it says
that a key in the child zone has some hash value, for some algorithm we
don't support. The DS RRset is signed, so we know it's not a forgery. We
can therefore be sure that there really is no way for us to validate the
DNSKEY RRset in the child zone, because of the actions of whoever has
the private key which signed the DS RRset.

Next stage, assume that the hash value for one of the DNSKEYS in the
child zone is correctly given by the DS record, but the signature
algorithm for the DNSKEY RRset is unknown to us. We have no way of
validating the DNSKEYs. We know that the DNSKEY which matches the hash
in the DS is good, but the others may be impostors. Indeed an attacker
may have given us an answer with one good DNSKEY (matching the DS hash)
and another DNSKEY for a non-implemented algorithm. If we use the rule
that non-implemented algorithm -> insecure, then that's enough for an
attacker to ensure that any zone becomes insecure, and records are
returned by the validator.

I think the rules should be that for unvalidated data, the zone should
be proved to be unsigned by NSEC/NSEC3 records for the DS. If a zone
isn't proved to be unsigned, and the data can't be validated, then the
data should be treated as BOGUS. If you don't do that then any attacker
can make any signed zone into an unsigned zone by the trick above, and
there's no point in a BOGUS return at all.
Post by Michał Kępień
Post by Simon Kelley
AFAIK there are no new hash algorithms out there. More likely the
problem is that a zone is _signed_ with a new public-key algorithm
(probably ECDSA) The following bit of 4035 might indicate that treating
such data as unisgned is not correct.
If for whatever reason none of the RRSIGs can be validated, the
response SHOULD be considered BAD. If the validation was being done
to service a recursive query, the name server MUST return RCODE 2 to
the originating client. However, it MUST return the full response if
and only if the original query had the CD bit set. Also see Section
4.7 on caching responses that do not validate.
Yes, the section you quoted is in there indeed, but its header reads
"Resolver Behavior When Signatures Do Not Validate". I believe there
may be a subtle difference between "signatures do not validate" and
"signatures cannot be validated".
I am not claiming my interpretation is the only valid one. While
researching this topic, I came across RFC 4955, which discusses the
methodology for setting up DNSSEC experiments which uses "strictly
unknown algorithm identifiers when signing the experimental zone, and
more importantly, having only unknown algorithm identifiers in the DS
records for the delegation to the zone at the parent". Section 4
contains the following quote discussing treating the zone as unsigned
Although this behavior isn't strictly mandatory (as marked by MUST),
it is unlikely for a validator to implement a substantially
different behavior. Essentially, if the validator does not have a
usable chain of trust to a child zone, then it can only do one of
two things: treat responses from the zone as insecure (the
recommended behavior), or treat the responses as bogus. If the
validator chooses the latter, this will both violate the expectation
of the zone owner and defeat the purpose of the above rule.
However, with local policy, it is within the right of a validator to
refuse to trust certain zones based on any criteria, including the
use of unknown signing algorithms.
I think that this is suggesting using unknown hash algorithms in the DS.
That makes perfect sense. Under the rule for such, "normal" validators
will treat such a DS as proof that the experimental zone is unsigned
(and so will dnsmasq, after the patch I posted, but test validators will
continue the validation into the zone.
Post by Michał Kępień
I haven't yet found another validating resolver in the wild that would
return SERVFAILs for zones signed using unknown algorithms, which is why
I'm so curious about your reasons for implementing it this way.
See above. I think there's a difference between "I know that zone isn't
signed" and "I can't prove that this zone has been correctly signed."

Cheers,

Simon.
Post by Michał Kępień
Post by Simon Kelley
http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=67ab3285b5d9a1b1e20e034cf272867fdab8a0f9
Thanks!
Michał Kępień
2015-11-25 07:40:59 UTC
Permalink
Post by Simon Kelley
Caveat. I'm not sure what the answer is. I'm certainly not arguing for a
fixed interpretation, not even the current behaviour of dnsmasq, and I'm
trying to understand what the correct behaviour should be. As always,
I'm terrified of breaking people's DNS by rejecting something that's OK,
and I'm also terrified or creating a security hole by accepting a answer
that's actually an attack which should be rejected.
I understand your position and I think that this is a fine approach.
Post by Simon Kelley
The difference between unknown DS hashes, and unknown signature is as
follows, as best I understand it. In the DS case, we have a DS RRset
which is signed, and validated. We know it has been signed, and it says
that a key in the child zone has some hash value, for some algorithm we
don't support. The DS RRset is signed, so we know it's not a forgery. We
can therefore be sure that there really is no way for us to validate the
DNSKEY RRset in the child zone, because of the actions of whoever has
the private key which signed the DS RRset.
Yes, I agree.
Post by Simon Kelley
Next stage, assume that the hash value for one of the DNSKEYS in the
child zone is correctly given by the DS record, but the signature
algorithm for the DNSKEY RRset is unknown to us. We have no way of
validating the DNSKEYs. We know that the DNSKEY which matches the hash
in the DS is good, but the others may be impostors. Indeed an attacker
may have given us an answer with one good DNSKEY (matching the DS hash)
and another DNSKEY for a non-implemented algorithm. If we use the rule
that non-implemented algorithm -> insecure, then that's enough for an
attacker to ensure that any zone becomes insecure, and records are
returned by the validator.
No, I don't think so. For the signature of the DNSKEY RRset to be
validated, it would need to be generated using the private counterpart
of the key whose hash has been published in the DS record in the parent
zone. RFC 4035 section 5.2 lists three prerequisites for authenticating
a DNSKEY RRset, the last of which is:

The matching DNSKEY RR in the child zone has the Zone Flag bit set,
the corresponding private key has signed the child zone's apex
DNSKEY RRset, and the resulting RRSIG RR authenticates the child
zone's apex DNSKEY RRset.

So taking a valid DNSKEY record, adding a different one with a bogus
algorithm and signing such a set with the private counterpart of the
bogus algorithm key is not enough for a properly operating validator to
authenticate such a set.

If you feel I am misunderstanding your argument or missing something, I
would be grateful if you could provide further explanations. Thanks for
bearing with me :)
--
Best regards,
Michał Kępień
Simon Kelley
2015-12-02 16:57:17 UTC
Permalink
Post by Michał Kępień
Post by Simon Kelley
Caveat. I'm not sure what the answer is. I'm certainly not arguing for a
fixed interpretation, not even the current behaviour of dnsmasq, and I'm
trying to understand what the correct behaviour should be. As always,
I'm terrified of breaking people's DNS by rejecting something that's OK,
and I'm also terrified or creating a security hole by accepting a answer
that's actually an attack which should be rejected.
I understand your position and I think that this is a fine approach.
Post by Simon Kelley
The difference between unknown DS hashes, and unknown signature is as
follows, as best I understand it. In the DS case, we have a DS RRset
which is signed, and validated. We know it has been signed, and it says
that a key in the child zone has some hash value, for some algorithm we
don't support. The DS RRset is signed, so we know it's not a forgery. We
can therefore be sure that there really is no way for us to validate the
DNSKEY RRset in the child zone, because of the actions of whoever has
the private key which signed the DS RRset.
Yes, I agree.
Post by Simon Kelley
Next stage, assume that the hash value for one of the DNSKEYS in the
child zone is correctly given by the DS record, but the signature
algorithm for the DNSKEY RRset is unknown to us. We have no way of
validating the DNSKEYs. We know that the DNSKEY which matches the hash
in the DS is good, but the others may be impostors. Indeed an attacker
may have given us an answer with one good DNSKEY (matching the DS hash)
and another DNSKEY for a non-implemented algorithm. If we use the rule
that non-implemented algorithm -> insecure, then that's enough for an
attacker to ensure that any zone becomes insecure, and records are
returned by the validator.
No, I don't think so. For the signature of the DNSKEY RRset to be
validated, it would need to be generated using the private counterpart
of the key whose hash has been published in the DS record in the parent
zone. RFC 4035 section 5.2 lists three prerequisites for authenticating
The matching DNSKEY RR in the child zone has the Zone Flag bit set,
the corresponding private key has signed the child zone's apex
DNSKEY RRset, and the resulting RRSIG RR authenticates the child
zone's apex DNSKEY RRset.
So taking a valid DNSKEY record, adding a different one with a bogus
algorithm and signing such a set with the private counterpart of the
bogus algorithm key is not enough for a properly operating validator to
authenticate such a set.
If you feel I am misunderstanding your argument or missing something, I
would be grateful if you could provide further explanations. Thanks for
bearing with me :)
I wish I was on firmer ground than I am in arguing this. Nevertheless,
if we keep going, we might be able to reach the conclusion.


Let's start with a concrete example:

***@holly:~/dnsmasq/dnsmasq$ dig @8.8.8.8 +dnssec isc.org

; <<>> DiG 9.9.5-3ubuntu0.5-Ubuntu <<>> @8.8.8.8 +dnssec isc.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19706
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;isc.org. IN A

;; ANSWER SECTION:
isc.org. 3 IN A 149.20.64.69
isc.org. 3 IN RRSIG A 5 2 60 20151231222650 20151201222650 6003
isc.org. j+b9GkjJBbzEi9tw6205EUqry+RC/7X2fjRHfB1KKxRXIMKj34h3RtY5
ng77uzNJ22qyTwXxboiqMkTYoSCD0l3hFwwrv/SDDneri+VKwN5wwxDA
5A75h03KfMxubg6N4cBDHHhWUp1Nim9pwge9xy3AUGorugA+WP0wvAfA iq0=

;; Query time: 65 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Dec 02 15:50:11 GMT 2015
;; MSG SIZE rcvd: 219

specifically

RRSIG A 5 2 60 ......

which says that this is a signature for algorithm type 5 (RSA/SHA-1)

to verify that this record is good we need the DNSKEY for isc.org, which
provides the public key. Public key/hash of data/signature and we're
good to go.

Now assume instead that we get a reply including as signature.

RRSIG A 100 2 60 ......

algorithm type 100! Can't verify that signature.

Now, if at this point there are three possible things to do.
1) BOGUS, return SERVFAIL. - the current dnsmasq behaviour.
2) do no further checking, return the reply, without an AD bit.
3) Attempt further checks on the DNSKEY with algo 100 and return answer
without AD bit is that pass.

Option 2 is what worries me. It allows an attacker to provide an answer
which will be returned to the original requestor, and which will likely
be used, even if the AD bit is not set.RRSIG A 5 2 60 ......

Option 3 seems the way to go, but what checks should be made? That the
DNSKEY for algo 100 exists? That's easy to spoof too. That it exists
and is validated by a DS record? That seems more hopeful. Maybe that's
the fix?

Cheers,

Simon.
Michał Kępień
2015-12-08 09:51:58 UTC
Permalink
Post by Simon Kelley
I wish I was on firmer ground than I am in arguing this. Nevertheless,
if we keep going, we might be able to reach the conclusion.
; (1 server found)
;; global options: +cmd
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19706
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
; EDNS: version: 0, flags: do; udp: 512
;isc.org. IN A
isc.org. 3 IN A 149.20.64.69
isc.org. 3 IN RRSIG A 5 2 60 20151231222650 20151201222650 6003
isc.org. j+b9GkjJBbzEi9tw6205EUqry+RC/7X2fjRHfB1KKxRXIMKj34h3RtY5
ng77uzNJ22qyTwXxboiqMkTYoSCD0l3hFwwrv/SDDneri+VKwN5wwxDA
5A75h03KfMxubg6N4cBDHHhWUp1Nim9pwge9xy3AUGorugA+WP0wvAfA iq0=
;; Query time: 65 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Dec 02 15:50:11 GMT 2015
;; MSG SIZE rcvd: 219
specifically
RRSIG A 5 2 60 ......
which says that this is a signature for algorithm type 5 (RSA/SHA-1)
to verify that this record is good we need the DNSKEY for isc.org, which
provides the public key. Public key/hash of data/signature and we're
good to go.
Now assume instead that we get a reply including as signature.
RRSIG A 100 2 60 ......
algorithm type 100! Can't verify that signature.
Now, if at this point there are three possible things to do.
1) BOGUS, return SERVFAIL. - the current dnsmasq behaviour.
2) do no further checking, return the reply, without an AD bit.
3) Attempt further checks on the DNSKEY with algo 100 and return answer
without AD bit is that pass.
Option 2 is what worries me. It allows an attacker to provide an answer
which will be returned to the original requestor, and which will likely
be used, even if the AD bit is not set.RRSIG A 5 2 60 ......
Option 3 seems the way to go, but what checks should be made? That the
DNSKEY for algo 100 exists?
Yes, that it exists within the (authenticated) DNSKEY RRset at the
zone's apex (for the example above: at isc.org. IN DNSKEY).
Post by Simon Kelley
That's easy to spoof too.
How so? For the DNSKEY RRset to be authenticated, it has to be signed
using the private key whose public counterpart's hash is exported into
the DS record in the parent zone, which itself has to be signed by the
parent etc. If an attacker inserts a rogue key into the DNSKEY RRset,
the latter needs to be signed with the correct private key. Returning
_only_ a signature made using a bogus algorithm (different than the one
given in the DS record) along with the malicious DNSKEY RRset is not
enough for the latter the be authenticated, so such responses should
definitely result in a SERVFAIL as they are likely a spoofing attempt.

However, if the zone's "righteous owner" decides to sign its DNSKEY
records using only algorithms the resolver does not understand, the
latter will learn that it won't be able to authenticate the DNSKEY RRset
the moment it checks the algorithms given in the DS record in the parent
zone. That should _not_ cause a SERVFAIL, because the chain of trust is
intact. Instead, that zone's records should be returned without the AD
bit set.
Post by Simon Kelley
That it exists
and is validated by a DS record? That seems more hopeful. Maybe that's
the fix?
RFC 4035, section 5.3.1 says that for any RRSIG in the zone to be
considered authentic:

...

o The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST
match the owner name, algorithm, and key tag for some DNSKEY RR in
the zone's apex DNSKEY RRset.

...

Note that this authentication process is only meaningful if the
validator authenticates the DNSKEY RR before using it to validate
signatures. The matching DNSKEY RR is considered to be authentic if:

o the apex DNSKEY RRset containing the DNSKEY RR is considered
authentic; or

o the RRset covered by the RRSIG RR is the apex DNSKEY RRset itself,
and the DNSKEY RR either matches an authenticated DS RR from the
parent zone or matches a trust anchor.

So for the example above, if the resolver receives an A RRset along with
an RRSIG using algorithm 100 and keytag X, it has to check whether there
is a DNSKEY RR using algorithm 100 and keytag X in the DNSKEY RRset at
that zone's apex. If there is (and the DNSKEY RRset is authenticated
and the resolver does not understand algorithm 100), the response should
be returned without setting the AD bit; if there isn't, that is a
possible spoofing attempt which should elicit a SERVFAIL.

As always, if anything I wrote above is not clear (or you believe I am
wrong), I will be happy to discuss this further.
--
Best regards,
Michał Kępień
Simon Kelley
2015-12-09 11:37:51 UTC
Permalink
Post by Michał Kępień
Post by Simon Kelley
I wish I was on firmer ground than I am in arguing this.
Nevertheless, if we keep going, we might be able to reach the
conclusion.
; (1 server found) ;; global options: +cmd ;; Got answer: ;;
qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 512 ;;
QUESTION SECTION: ;isc.org. IN A
;; ANSWER SECTION: isc.org. 3 IN A 149.20.64.69 isc.org. 3 IN
RRSIG A 5 2 60 20151231222650 20151201222650 6003 isc.org.
j+b9GkjJBbzEi9tw6205EUqry+RC/7X2fjRHfB1KKxRXIMKj34h3RtY5
ng77uzNJ22qyTwXxboiqMkTYoSCD0l3hFwwrv/SDDneri+VKwN5wwxDA
5A75h03KfMxubg6N4cBDHHhWUp1Nim9pwge9xy3AUGorugA+WP0wvAfA iq0=
Wed Dec 02 15:50:11 GMT 2015 ;; MSG SIZE rcvd: 219
specifically
RRSIG A 5 2 60 ......
which says that this is a signature for algorithm type 5
(RSA/SHA-1)
to verify that this record is good we need the DNSKEY for
isc.org, which provides the public key. Public key/hash of
data/signature and we're good to go.
Now assume instead that we get a reply including as signature.
RRSIG A 100 2 60 ......
algorithm type 100! Can't verify that signature.
Now, if at this point there are three possible things to do. 1)
BOGUS, return SERVFAIL. - the current dnsmasq behaviour. 2) do no
further checking, return the reply, without an AD bit. 3) Attempt
further checks on the DNSKEY with algo 100 and return answer
without AD bit is that pass.
Option 2 is what worries me. It allows an attacker to provide an
answer which will be returned to the original requestor, and
which will likely be used, even if the AD bit is not set.RRSIG A
5 2 60 ......
Option 3 seems the way to go, but what checks should be made?
That the DNSKEY for algo 100 exists?
Yes, that it exists within the (authenticated) DNSKEY RRset at the
zone's apex (for the example above: at isc.org. IN DNSKEY).
Post by Simon Kelley
That's easy to spoof too.
How so? For the DNSKEY RRset to be authenticated, it has to be
signed using the private key whose public counterpart's hash is
exported into the DS record in the parent zone, which itself has to
be signed by the parent etc. If an attacker inserts a rogue key
into the DNSKEY RRset, the latter needs to be signed with the
correct private key. Returning _only_ a signature made using a
bogus algorithm (different than the one given in the DS record)
along with the malicious DNSKEY RRset is not enough for the latter
the be authenticated, so such responses should definitely result in
a SERVFAIL as they are likely a spoofing attempt.
However, if the zone's "righteous owner" decides to sign its
DNSKEY records using only algorithms the resolver does not
understand, the latter will learn that it won't be able to
authenticate the DNSKEY RRset the moment it checks the algorithms
given in the DS record in the parent zone. That should _not_ cause
a SERVFAIL, because the chain of trust is intact. Instead, that
zone's records should be returned without the AD bit set.
Post by Simon Kelley
That it exists and is validated by a DS record? That seems more
hopeful. Maybe that's the fix?
RFC 4035, section 5.3.1 says that for any RRSIG in the zone to be
...
o The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields
MUST match the owner name, algorithm, and key tag for some DNSKEY
RR in the zone's apex DNSKEY RRset.
...
Note that this authentication process is only meaningful if the
validator authenticates the DNSKEY RR before using it to validate
o the apex DNSKEY RRset containing the DNSKEY RR is considered
authentic; or
o the RRset covered by the RRSIG RR is the apex DNSKEY RRset
itself, and the DNSKEY RR either matches an authenticated DS RR
from the parent zone or matches a trust anchor.
So for the example above, if the resolver receives an A RRset along
with an RRSIG using algorithm 100 and keytag X, it has to check
whether there is a DNSKEY RR using algorithm 100 and keytag X in
the DNSKEY RRset at that zone's apex. If there is (and the DNSKEY
RRset is authenticated and the resolver does not understand
algorithm 100), the response should be returned without setting the
AD bit; if there isn't, that is a possible spoofing attempt which
should elicit a SERVFAIL.
As always, if anything I wrote above is not clear (or you believe I
am wrong), I will be happy to discuss this further.
This is starting to make much more sense to me now.


It seems to me that there may be a very simple way to implement this,
which is to simply disregard signatures for unknown algorithms. Thus
the answer is treated in the same way as an unsigned answer: proof is
sought that the zone is unsigned. At the moment this is done by
looking for proof of absence of a DS record (NSEC or NSEC3). Extending
this to aceept a validated DS record with unknown hash or unknown
algorithm make things work in this case.

Later: there may be a problem with the above, under the following
circumstance.

1) The DNKEY RRset contains at least two keys: one for a known
algorithm and one for the unknown algorithm in the original answer.

2) The DNSKEY RRset is signed using the known algorithm.

3) The key for the known algorithm is validated by the hash of the
corresponding DS record.

My test above would fail to determine that an unsigned answer was
legitimate under such circumstances and needs to be extended to
account for that case.

Sound reasonable?

Cheers,

Simon.
Michał Kępień
2015-12-09 12:32:43 UTC
Permalink
Post by Simon Kelley
This is starting to make much more sense to me now.
It seems to me that there may be a very simple way to implement this,
which is to simply disregard signatures for unknown algorithms. Thus
the answer is treated in the same way as an unsigned answer: proof is
sought that the zone is unsigned. At the moment this is done by
looking for proof of absence of a DS record (NSEC or NSEC3). Extending
this to aceept a validated DS record with unknown hash or unknown
algorithm make things work in this case.
Later: there may be a problem with the above, under the following
circumstance.
1) The DNKEY RRset contains at least two keys: one for a known
algorithm and one for the unknown algorithm in the original answer.
2) The DNSKEY RRset is signed using the known algorithm.
3) The key for the known algorithm is validated by the hash of the
corresponding DS record.
My test above would fail to determine that an unsigned answer was
legitimate under such circumstances and needs to be extended to
account for that case.
Sound reasonable?
Yes, I agree with everything you wrote above.
--
Best regards,
Michał Kępień
Simon Kelley
2015-12-16 13:48:19 UTC
Permalink
OK. Fixing this turned into a marathon re-write session. The result is
a huge improvement: by doing the core things right I've vastly
simplified the code and made it much easier to understand and modify.

The final patch, to make a zone which has a valid key of an
unsupported algorithm count as insecure, even if also has a key with a
supported algorithm which is validated by the DS record, Just went in.
It's gratifyingly small and simple.

All the new code is in the git repo. Please test and play.


Cheers,

Simon.
Post by Michał Kępień
Post by Simon Kelley
This is starting to make much more sense to me now.
It seems to me that there may be a very simple way to implement
this, which is to simply disregard signatures for unknown
algorithms. Thus the answer is treated in the same way as an
unsigned answer: proof is sought that the zone is unsigned. At
the moment this is done by looking for proof of absence of a DS
record (NSEC or NSEC3). Extending this to aceept a validated DS
record with unknown hash or unknown algorithm make things work in
this case.
Later: there may be a problem with the above, under the
following circumstance.
1) The DNKEY RRset contains at least two keys: one for a known
algorithm and one for the unknown algorithm in the original
answer.
2) The DNSKEY RRset is signed using the known algorithm.
3) The key for the known algorithm is validated by the hash of
the corresponding DS record.
My test above would fail to determine that an unsigned answer
was legitimate under such circumstances and needs to be extended
to account for that case.
Sound reasonable?
Yes, I agree with everything you wrote above.
Michał Kępień
2015-12-17 14:31:27 UTC
Permalink
Post by Simon Kelley
OK. Fixing this turned into a marathon re-write session. The result is
a huge improvement: by doing the core things right I've vastly
simplified the code and made it much easier to understand and modify.
The final patch, to make a zone which has a valid key of an
unsupported algorithm count as insecure, even if also has a key with a
supported algorithm which is validated by the DS record, Just went in.
It's gratifyingly small and simple.
All the new code is in the git repo. Please test and play.
Thank you for your work. I did some testing and, unfortunately, found
numerous issues with the latest master. I'll describe them below,
ordered by decreasing importance (IMHO of course).

First and foremost, the primary issue of returning SERVFAILs for zones
with DNSKEY RRsets signed using _only_ unknown algorithms (even though
we are able to hash the DNSKEY RRset and authenticate the DS at the
parent) is not resolved. Quick example: if you try to resolve anything
within a zone signed using only ECC-GOST keys, with an SHA-256 DS at the
parent, SERVFAIL is still returned as dnssec_validate_by_ds() still
requires the validate_rrset() call for DNSKEY RRset to succeed for it to
return anything else than STAT_BOGUS. In my patch which started this
thread I tried to demonstrate that if all DNSKEY RRset validations fail
only due to the lack of support for the _signing_ algorithms used by its
RRSIGs, the zone should be marked as insecure, not bogus.

Then there's commit 2dbba34, whose log message says: "A zone which has
at least one key with an algorithm we don't support should be considered
as insecure." That's not right. Only a zone whose _all_ keys use
algorithms we don't support should be considered as insecure. AFAICT,
the implementation follows the commit message, i.e. if
algo_digest_name() returns NULL for any of the DNSKEY RRs cached for
this zone, zone_status() will immediately return STAT_INSECURE. But why
would it? Even if one DNSKEY RR uses a weird algorithm, others may use
algorithms we understand.

Next up, there's a nasty bug in dnssec_validate_ds() caused by improper
closing brace placement:

if (!neganswer)
{
cache_start_insert();

for (i = 0; i < ntohs(header->ancount); i++)
{
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
return STAT_BOGUS; /* bad packet */

GETSHORT(atype, p);
GETSHORT(aclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);

if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */

if (aclass == class && atype == T_DS && rc == 1)
{
int algo, digest, keytag;
unsigned char *psave = p;
...
p = psave;

if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */
}

cache_end_insert();
}

}

The closing brace present after cache_end_insert() should be moved just
below the line which takes care of restoring p to a saved value.
Otherwise, two things happen:

* DS records with multiple RRSIGs returned in the answer will be
considered bogus due to the GETx() calls parsing rubbish (first
RRSIG's RDATA) in the third iteration of the for loop,

* only the first DS RR for a zone will be cached, which causes issues
for zones with more than one DS RR at the parent.

Patch follows (whitespace not corrected for clarity):

------------------------------------------------------------------------
diff --git a/src/dnssec.c b/src/dnssec.c
index dc563e0..b8630d7 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -1224,13 +1224,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
}

p = psave;
+ }

if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */
}

cache_end_insert();
- }
}
else
{
------------------------------------------------------------------------

While debugging the latest code, I also noticed that the algorithm
support checks seem inconsistent. AFAICT, checking whether a given
signing algorithm is supported is done using algo_digest_name(). What
that function actually checks is whether the hashing algorithm used by
the given signing scheme is supported, not whether the actual signing
scheme is supported. Let's compare the behavior for ECC-GOST and ECDSA.
For ECC-GOST, algo_digest_name() will always return "gosthash94", which
the caller interprets as "this signing scheme is supported". However,
verify() will always return 0 for algorithm 12, because ECC-GOST signing
isn't actually implemented. Assume now that dnsmasq was compiled with
-DNO_NETTLE_ECC. If you ask algo_digest_name() about ECDSA support, it
will immediately tell the caller that "this signing scheme is _not_
supported", even though libnettle wouldn't have problems with
calculating a SHA-256/SHA-384 hash. This is not that big of an issue,
but it hindered my debugging efforts.

Finally, you used STAT_NEED_DNSKEY in the comments while the actual name
of that constant in src/dnsmasq.h is STAT_NEED_KEY.

Hope this helps. As always, please let me know if anything above is not
clear or my interpretation is incorrect.
--
Best regards,
Michał Kępień
Simon Kelley
2015-12-17 17:35:59 UTC
Permalink
Post by Michał Kępień
Post by Simon Kelley
OK. Fixing this turned into a marathon re-write session. The
result is a huge improvement: by doing the core things right I've
vastly simplified the code and made it much easier to understand
and modify.
The final patch, to make a zone which has a valid key of an
unsupported algorithm count as insecure, even if also has a key
with a supported algorithm which is validated by the DS record,
Just went in. It's gratifyingly small and simple.
All the new code is in the git repo. Please test and play.
Thank you for your work. I did some testing and, unfortunately,
found numerous issues with the latest master. I'll describe them
below, ordered by decreasing importance (IMHO of course).
First and foremost, the primary issue of returning SERVFAILs for
zones with DNSKEY RRsets signed using _only_ unknown algorithms
(even though we are able to hash the DNSKEY RRset and authenticate
the DS at the parent) is not resolved. Quick example: if you try
to resolve anything within a zone signed using only ECC-GOST keys,
with an SHA-256 DS at the parent, SERVFAIL is still returned as
dnssec_validate_by_ds() still requires the validate_rrset() call
for DNSKEY RRset to succeed for it to return anything else than
STAT_BOGUS. In my patch which started this thread I tried to
demonstrate that if all DNSKEY RRset validations fail only due to
the lack of support for the _signing_ algorithms used by its
RRSIGs, the zone should be marked as insecure, not bogus.
Did you test this on a real domain and see a failure? The way it's
intended to work is that the call to zone_status() at line 2042 will
work down from the root to the DS, where it will find that the SHA-256
DS covers a ECC-GOST key and return STAT_INSECURE at line 1869. That
will be returned from dnssec_validate_reply at line 2049, before the
call to validate_rrset is even made. dnssec_validate_by_ds() should
never be called.

An example of a domain that fails here would be really useful. I did
testing by removing algorithms but as there are no rare algorithms
it's difficult not to cause early failure of the process before the
test case is reached.
Post by Michał Kępień
Then there's commit 2dbba34, whose log message says: "A zone which
has at least one key with an algorithm we don't support should be
considered as insecure."
That's not right. Only a zone whose _all_ keys use algorithms we
don't support should be considered as insecure.
No, a zone may have a key we support but records are signed only using
the key we don't support.

If a zone has a key of a type we don't understand, than we must accept
an insecure answer, (one signed only with the unsupported algorithm)
so an insecure result is legitimate for that zone. In the case that
the records are actually signed by an algorithm we do support, a
secure result is possible and an insecure response is sub-optimal, but
it's not wrong.
Post by Michał Kępień
AFAICT, the implementation follows the commit message, i.e. if
algo_digest_name() returns NULL for any of the DNSKEY RRs cached
for this zone, zone_status() will immediately return STAT_INSECURE.
But why would it? Even if one DNSKEY RR uses a weird algorithm,
others may use algorithms we understand.
That's correct.
Post by Michał Kępień
Next up, there's a nasty bug in dnssec_validate_ds() caused by
if (!neganswer) { cache_start_insert();
for (i = 0; i < ntohs(header->ancount); i++) { if (!(rc =
extract_name(header, plen, &p, name, 0, 10))) return STAT_BOGUS; /*
bad packet */
GETSHORT(atype, p); GETSHORT(aclass, p); GETLONG(ttl, p);
GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen)) return STAT_BOGUS; /* bad
packet */
if (aclass == class && atype == T_DS && rc == 1) { int algo,
digest, keytag; unsigned char *psave = p; ... p = psave;
if (!ADD_RDLEN(header, p, plen, rdlen)) return STAT_BOGUS; /* bad
packet */ }
cache_end_insert(); }
}
The closing brace present after cache_end_insert() should be moved
just below the line which takes care of restoring p to a saved
* DS records with multiple RRSIGs returned in the answer will be
considered bogus due to the GETx() calls parsing rubbish (first
RRSIG's RDATA) in the third iteration of the for loop,
* only the first DS RR for a zone will be cached, which causes
issues for zones with more than one DS RR at the parent.
----------------------------------------------------------------------
- --
diff --git a/src/dnssec.c b/src/dnssec.c
Post by Michał Kępień
index dc563e0..b8630d7 100644 --- a/src/dnssec.c +++
dnssec_validate_ds(time_t now, struct dns_header *header, size_t
plen, char } p = psave; + } if (!ADD_RDLEN(header, p, plen,
rdlen)) return STAT_BOGUS; /* bad packet */ } cache_end_insert();
- } } else {
----------------------------------------------------------------------
- --
Post by Michał Kępień
While debugging the latest code, I also noticed that the
algorithm support checks seem inconsistent. AFAICT, checking
whether a given signing algorithm is supported is done using
algo_digest_name(). What that function actually checks is whether
the hashing algorithm used by the given signing scheme is
supported, not whether the actual signing scheme is supported.
Let's compare the behavior for ECC-GOST and ECDSA. For ECC-GOST,
algo_digest_name() will always return "gosthash94", which the
caller interprets as "this signing scheme is supported". However,
verify() will always return 0 for algorithm 12, because ECC-GOST
signing isn't actually implemented. Assume now that dnsmasq was
compiled with -DNO_NETTLE_ECC. If you ask algo_digest_name() about
ECDSA support, it will immediately tell the caller that "this
signing scheme is _not_ supported", even though libnettle wouldn't
have problems with calculating a SHA-256/SHA-384 hash. This is not
that big of an issue, but it hindered my debugging efforts.
That's a good point. The problem is that Nettle supports introspection
for hash functions, but not public-key signatures. algo_digest_name()
uses the introspection, but it doesn't tell you that algorithms 13 and
14 are not available because ECC is not available. Hence I added the
#ifdef NO_NETTLE_ECC, which should encompass 12. Even that's not
right, because 12 needs ECC-GOST, which isn't implemented. The
canonical place for this information is actually verify(): it knows
which algorithms are OK. I've fixed the code to do that. A digest is
supported if it's in the switch in algo_digest_name() AND supported by
the current Nettle. A signature algo is supported if it's in the
switch in verify_func(). That has to be kept up-to-date with Nettle's
capabilities.
Post by Michał Kępień
Finally, you used STAT_NEED_DNSKEY in the comments while the actual
name of that constant in src/dnsmasq.h is STAT_NEED_KEY.
Fixed, thanks.

I pushed these changes. I'm looking at how to determine a secure
answer when a zone may be insecure through use of an unsupported
algorithm.


Cheers,

Simon.
Post by Michał Kępień
Hope this helps. As always, please let me know if anything above
is not clear or my interpretation is incorrect.
Simon Kelley
2015-12-17 17:58:24 UTC
Permalink
Post by Simon Kelley
Post by Michał Kępień
First and foremost, the primary issue of returning SERVFAILs for
zones with DNSKEY RRsets signed using _only_ unknown algorithms
(even though we are able to hash the DNSKEY RRset and
authenticate the DS at the parent) is not resolved. Quick
example: if you try to resolve anything within a zone signed
using only ECC-GOST keys, with an SHA-256 DS at the parent,
SERVFAIL is still returned as dnssec_validate_by_ds() still
requires the validate_rrset() call for DNSKEY RRset to succeed
for it to return anything else than STAT_BOGUS. In my patch
which started this thread I tried to demonstrate that if all
DNSKEY RRset validations fail only due to the lack of support for
the _signing_ algorithms used by its RRSIGs, the zone should be
marked as insecure, not bogus.
Did you test this on a real domain and see a failure? The way it's
intended to work is that the call to zone_status() at line 2042
will work down from the root to the DS, where it will find that the
SHA-256 DS covers a ECC-GOST key and return STAT_INSECURE at line
1869. That will be returned from dnssec_validate_reply at line
2049, before the call to validate_rrset is even made.
dnssec_validate_by_ds() should never be called.
An example of a domain that fails here would be really useful. I
did testing by removing algorithms but as there are no rare
algorithms it's difficult not to cause early failure of the process
before the test case is reached.
More: the easiest way to test this is to use cloudflare zones, which
are signed using ECC. If I resolve www.ietf.org, which is a CNAME to
the cloudflare CDN, in dnsmasq I get a SECURE result. If I compile
dnsmasq with -DNO_NETTLE_ECC and repeat the test, I get in INSECURE
result. Looking good!


Cheers,

Simon.
Michał Kępień
2015-12-18 13:22:49 UTC
Permalink
Post by Simon Kelley
Post by Michał Kępień
First and foremost, the primary issue of returning SERVFAILs for
zones with DNSKEY RRsets signed using _only_ unknown algorithms
(even though we are able to hash the DNSKEY RRset and authenticate
the DS at the parent) is not resolved. Quick example: if you try
to resolve anything within a zone signed using only ECC-GOST keys,
with an SHA-256 DS at the parent, SERVFAIL is still returned as
dnssec_validate_by_ds() still requires the validate_rrset() call
for DNSKEY RRset to succeed for it to return anything else than
STAT_BOGUS. In my patch which started this thread I tried to
demonstrate that if all DNSKEY RRset validations fail only due to
the lack of support for the _signing_ algorithms used by its
RRSIGs, the zone should be marked as insecure, not bogus.
Did you test this on a real domain and see a failure? The way it's
intended to work is that the call to zone_status() at line 2042 will
work down from the root to the DS, where it will find that the SHA-256
DS covers a ECC-GOST key and return STAT_INSECURE at line 1869. That
will be returned from dnssec_validate_reply at line 2049, before the
call to validate_rrset is even made. dnssec_validate_by_ds() should
never be called.
An example of a domain that fails here would be really useful. I did
testing by removing algorithms but as there are no rare algorithms
it's difficult not to cause early failure of the process before the
test case is reached.
Ah, it seems the bugs were intertwined. You see, before 14a4ae8 we
could not tell ECC-GOST is not supported until an actual attempt was
made to call verify() with algorithm 12, so dnssec_validate_by_ds() was
called for the DNSKEY RRset after zone_status() "verified" that ECC-GOST
is supported. With the latest master this is no longer the case and the
zone is processed exactly the way you described above. Though the
algorithm is still flawed, see below.
Post by Simon Kelley
Post by Michał Kępień
Then there's commit 2dbba34, whose log message says: "A zone which
has at least one key with an algorithm we don't support should be
considered as insecure."
That's not right. Only a zone whose _all_ keys use algorithms we
don't support should be considered as insecure.
No, a zone may have a key we support but records are signed only using
the key we don't support.
If a zone has a key of a type we don't understand, than we must accept
an insecure answer, (one signed only with the unsupported algorithm)
so an insecure result is legitimate for that zone. In the case that
the records are actually signed by an algorithm we do support, a
secure result is possible and an insecure response is sub-optimal, but
it's not wrong.
Could you please provide a specific example? Because I believe every
such zone would violate RFC 4035, section 2.2, which says:

There MUST be an RRSIG for each RRset using at least one DNSKEY of
each algorithm in the zone apex DNSKEY RRset. The apex DNSKEY RRset
itself MUST be signed by each algorithm appearing in the DS RRset
located at the delegating parent (if any).

Assume we authenticated the DS RRset at the parent and that it only
contains one hash of a key using a supported signing algorithm. That
means that the relevant key using that algorithm has to be present in
the zone apex DNSKEY RRset. Now, if we create another key with an
unsupported algorithm and insert it into the zone apex DNSKEY RRset, we
are free to sign that zone's records with it, but we still have to
ensure that all records also have an RRSIG using the algorithm of the
other key.

Try the following: compile dnsmasq with -DNO_NETTLE_ECC and then ask it
for the DNSKEY RRset at dnssec-test.org. You will notice that the
answer incorrectly lacks the AD bit. This zone has a single DS at the
parent, pointing to an RSA/SHA-1 key. Its DNSKEY RRset contains one RSA
key (corresponding to the DS) and one ECDSA key. All records in this
zone are signed with both keys to fulfill the requirement specified by
RFC 4035, section 2.2. As dnsmasq supports RSA/SHA-1, why would it
treat the whole zone as insecure for the sole reason that one of the
authenticated DNSKEY RRs is using an algorithm it doesn't understand?

I also noticed that the DS processing loop in zone_status() is flawed as
well. It returns STAT_INSECURE whenever it encounters even a single
hash/signing algorithm it doesn't support. Why? If there are two or
more DS RRs for the same keytag-algorithm pair (so only the hashing
algorithms are different), why would the zone be considered insecure,
given that we understand at least one hashing algorithm of those used by
the DS RRs?

Moreover, I believe we can break out of that loop immediately after
setting secure_ds to 1 as finding one DS RR whose hashing and signing
algorithms are both supported is enough to be sure that we will be able
to attempt validation of at least one of the zone's DNSKEY RRs (as long
as it actually exists in the zone, of course, but we're checking that
anyway in dnssec_validate_by_ds()).

A useful test subject for these issues is the caint.su zone, which uses
two keys, each using a different algorithm (RSA and ECC-GOST) and also
provides three separate hashes of each of those keys in its parent zone.
Using this zone as an example for the reasoning above, it shouldn't be
considered insecure just because we don't understand GOST hashes and/or
signing.
Post by Simon Kelley
Post by Michał Kępień
While debugging the latest code, I also noticed that the
algorithm support checks seem inconsistent. AFAICT, checking
whether a given signing algorithm is supported is done using
algo_digest_name(). What that function actually checks is whether
the hashing algorithm used by the given signing scheme is
supported, not whether the actual signing scheme is supported.
Let's compare the behavior for ECC-GOST and ECDSA. For ECC-GOST,
algo_digest_name() will always return "gosthash94", which the
caller interprets as "this signing scheme is supported". However,
verify() will always return 0 for algorithm 12, because ECC-GOST
signing isn't actually implemented. Assume now that dnsmasq was
compiled with -DNO_NETTLE_ECC. If you ask algo_digest_name() about
ECDSA support, it will immediately tell the caller that "this
signing scheme is _not_ supported", even though libnettle wouldn't
have problems with calculating a SHA-256/SHA-384 hash. This is not
that big of an issue, but it hindered my debugging efforts.
That's a good point. The problem is that Nettle supports introspection
for hash functions, but not public-key signatures. algo_digest_name()
uses the introspection, but it doesn't tell you that algorithms 13 and
14 are not available because ECC is not available. Hence I added the
#ifdef NO_NETTLE_ECC, which should encompass 12. Even that's not
right, because 12 needs ECC-GOST, which isn't implemented. The
canonical place for this information is actually verify(): it knows
which algorithms are OK. I've fixed the code to do that. A digest is
supported if it's in the switch in algo_digest_name() AND supported by
the current Nettle. A signature algo is supported if it's in the
switch in verify_func(). That has to be kept up-to-date with Nettle's
capabilities.
Thanks, as I already stated above, this fixed quite some of the problems
I observed.
--
Best regards,
Michał Kępień
Simon Kelley
2015-12-18 18:37:57 UTC
Permalink
Post by Michał Kępień
Post by Simon Kelley
Post by Michał Kępień
First and foremost, the primary issue of returning SERVFAILs
for zones with DNSKEY RRsets signed using _only_ unknown
algorithms (even though we are able to hash the DNSKEY RRset
and authenticate the DS at the parent) is not resolved. Quick
example: if you try to resolve anything within a zone signed
using only ECC-GOST keys, with an SHA-256 DS at the parent,
SERVFAIL is still returned as dnssec_validate_by_ds() still
requires the validate_rrset() call for DNSKEY RRset to succeed
for it to return anything else than STAT_BOGUS. In my patch
which started this thread I tried to demonstrate that if all
DNSKEY RRset validations fail only due to the lack of support
for the _signing_ algorithms used by its RRSIGs, the zone
should be marked as insecure, not bogus.
Did you test this on a real domain and see a failure? The way
it's intended to work is that the call to zone_status() at line
2042 will work down from the root to the DS, where it will find
that the SHA-256 DS covers a ECC-GOST key and return
STAT_INSECURE at line 1869. That will be returned from
dnssec_validate_reply at line 2049, before the call to
validate_rrset is even made. dnssec_validate_by_ds() should never
be called.
An example of a domain that fails here would be really useful. I
did testing by removing algorithms but as there are no rare
algorithms it's difficult not to cause early failure of the
process before the test case is reached.
Ah, it seems the bugs were intertwined. You see, before 14a4ae8
we could not tell ECC-GOST is not supported until an actual attempt
was made to call verify() with algorithm 12, so
dnssec_validate_by_ds() was called for the DNSKEY RRset after
zone_status() "verified" that ECC-GOST is supported. With the
latest master this is no longer the case and the zone is processed
exactly the way you described above. Though the algorithm is still
flawed, see below.
Post by Simon Kelley
Post by Michał Kępień
Then there's commit 2dbba34, whose log message says: "A zone
which has at least one key with an algorithm we don't support
should be considered as insecure."
That's not right. Only a zone whose _all_ keys use algorithms
we don't support should be considered as insecure.
No, a zone may have a key we support but records are signed only
using the key we don't support.
If a zone has a key of a type we don't understand, than we must
accept an insecure answer, (one signed only with the unsupported
algorithm) so an insecure result is legitimate for that zone. In
the case that the records are actually signed by an algorithm we
do support, a secure result is possible and an insecure response
is sub-optimal, but it's not wrong.
Could you please provide a specific example? Because I believe
There MUST be an RRSIG for each RRset using at least one DNSKEY of
each algorithm in the zone apex DNSKEY RRset. The apex DNSKEY
RRset itself MUST be signed by each algorithm appearing in the DS
RRset located at the delegating parent (if any).
That's very useful, and contradicts what I thought I understood from
our earlier conversation. Given that information, then you're
completely right. The test should return an INSECURE result only of
NONE of the algorithms are supported.
Post by Michał Kępień
Assume we authenticated the DS RRset at the parent and that it
only contains one hash of a key using a supported signing
algorithm. That means that the relevant key using that algorithm
has to be present in the zone apex DNSKEY RRset. Now, if we create
another key with an unsupported algorithm and insert it into the
zone apex DNSKEY RRset, we are free to sign that zone's records
with it, but we still have to ensure that all records also have an
RRSIG using the algorithm of the other key.
Try the following: compile dnsmasq with -DNO_NETTLE_ECC and then
ask it for the DNSKEY RRset at dnssec-test.org. You will notice
that the answer incorrectly lacks the AD bit. This zone has a
single DS at the parent, pointing to an RSA/SHA-1 key. Its DNSKEY
RRset contains one RSA key (corresponding to the DS) and one ECDSA
key. All records in this zone are signed with both keys to fulfill
the requirement specified by RFC 4035, section 2.2. As dnsmasq
supports RSA/SHA-1, why would it treat the whole zone as insecure
for the sole reason that one of the authenticated DNSKEY RRs is
using an algorithm it doesn't understand?
I also noticed that the DS processing loop in zone_status() is
flawed as well. It returns STAT_INSECURE whenever it encounters
even a single hash/signing algorithm it doesn't support. Why? If
there are two or more DS RRs for the same keytag-algorithm pair (so
only the hashing algorithms are different), why would the zone be
considered insecure, given that we understand at least one hashing
algorithm of those used by the DS RRs?
A good point, but made irrelevant by our insight above. That code is
attempting to cover the case that an RRset in the zone may be signed
only by an algorithm we can't understand. Given the 4035 paragraph you
quoted, the actual inference is that if there's a DS with both hash
and sig algos we understand, then we can assmune that 1) we are able
to prove the DNSKEY RRSET is good and 2) we'll find an RRSIG for an
algorithm we understand for any RRset in the zone. The checks after
"if (secure_ds) need to go completely.
Post by Michał Kępień
Moreover, I believe we can break out of that loop immediately
after setting secure_ds to 1 as finding one DS RR whose hashing and
signing algorithms are both supported is enough to be sure that we
will be able to attempt validation of at least one of the zone's
DNSKEY RRs (as long as it actually exists in the zone, of course,
but we're checking that anyway in dnssec_validate_by_ds()).
A useful test subject for these issues is the caint.su zone, which
uses two keys, each using a different algorithm (RSA and ECC-GOST)
and also provides three separate hashes of each of those keys in
its parent zone. Using this zone as an example for the reasoning
above, it shouldn't be considered insecure just because we don't
understand GOST hashes and/or signing.
That's a good test case, thanks.
Post by Michał Kępień
Thanks, as I already stated above, this fixed quite some of the
problems I observed.
I'll make further changes ASAP and get back to you.


Cheers,

Simon.
Simon Kelley
2015-12-20 21:40:12 UTC
Permalink
Post by Michał Kępień
A useful test subject for these issues is the caint.su zone, which
uses two keys, each using a different algorithm (RSA and ECC-GOST)
and also provides three separate hashes of each of those keys in
its parent zone. Using this zone as an example for the reasoning
above, it shouldn't be considered insecure just because we don't
understand GOST hashes and/or signing.
I just checked in code that behaves well in this case. This is making
real progress: thanks for your assistance.
Post by Michał Kępień
Post by Simon Kelley
That's a good point. The problem is that Nettle supports
introspection for hash functions, but not public-key signatures.
algo_digest_name() uses the introspection, but it doesn't tell
you that algorithms 13 and 14 are not available because ECC is
not available. Hence I added the #ifdef NO_NETTLE_ECC, which
should encompass 12. Even that's not right, because 12 needs
ECC-GOST, which isn't implemented. The canonical place for this
information is actually verify(): it knows which algorithms are
OK. I've fixed the code to do that. A digest is supported if
it's in the switch in algo_digest_name() AND supported by the
current Nettle. A signature algo is supported if it's in the
switch in verify_func(). That has to be kept up-to-date with
Nettle's capabilities.
Thanks, as I already stated above, this fixed quite some of the
problems I observed.
I extended this to include a function defining known NSEC3 hash
functions. There's only one at the moment, but now we're prepared!
(and in the case that a validator doesn't support an NSEC3 hash
function, we're told by RFC 5155 8.1 to return BOGUS.


Cheers,

Simon.
Michał Kępień
2015-12-21 14:21:12 UTC
Permalink
Post by Simon Kelley
Post by Michał Kępień
A useful test subject for these issues is the caint.su zone, which
uses two keys, each using a different algorithm (RSA and ECC-GOST)
and also provides three separate hashes of each of those keys in
its parent zone. Using this zone as an example for the reasoning
above, it shouldn't be considered insecure just because we don't
understand GOST hashes and/or signing.
I just checked in code that behaves well in this case. This is making
real progress: thanks for your assistance.
Well, thanks to you for doing all the heavy lifting. It's been a fun
ride.

I did some preliminary tests on the latest master and things are looking
good. If I find any glitches, I'll report them.

The only remark I have this time is that it might be nice to also
include digest/signing algorithms in DS query logs. Seeing something
like this in your logs can be confusing:

reply caint.su is DS keytag 697
reply caint.su is DS keytag 697
reply caint.su is DS keytag 697 (not supported)

Instead, something like this could be written:

reply caint.su is DS keytag 697, algo 5, digest 1
reply caint.su is DS keytag 697, algo 5, digest 2
reply caint.su is DS keytag 697, algo 5, digest 3 (not supported)

It's just a thought, though.
--
Best regards,
Michał Kępień
Simon Kelley
2015-12-30 22:36:02 UTC
Permalink
Post by Michał Kępień
The only remark I have this time is that it might be nice to also
include digest/signing algorithms in DS query logs. Seeing
reply caint.su is DS keytag 697 reply caint.su is DS keytag 697
reply caint.su is DS keytag 697 (not supported)
reply caint.su is DS keytag 697, algo 5, digest 1 reply caint.su is
DS keytag 697, algo 5, digest 2 reply caint.su is DS keytag 697,
algo 5, digest 3 (not supported)
It's just a thought, though.
OK, extra logging is there now.

Cheers,

Simon.

Loading...