Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
D
Dnsmasq
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nanahira
Dnsmasq
Commits
0fc2f313
Commit
0fc2f313
authored
Jan 08, 2014
by
Simon Kelley
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
First functional DNSSEC - highly alpha.
parent
c3e0b9b6
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
674 additions
and
692 deletions
+674
-692
src/cache.c
src/cache.c
+66
-17
src/config.h
src/config.h
+8
-3
src/dnsmasq.h
src/dnsmasq.h
+24
-5
src/dnssec-crypto.h
src/dnssec-crypto.h
+4
-4
src/dnssec-openssl.c
src/dnssec-openssl.c
+19
-21
src/dnssec.c
src/dnssec.c
+359
-598
src/forward.c
src/forward.c
+71
-33
src/option.c
src/option.c
+30
-2
src/rfc1035.c
src/rfc1035.c
+31
-7
src/util.c
src/util.c
+62
-2
No files found.
src/cache.c
View file @
0fc2f313
...
...
@@ -56,6 +56,8 @@ static const struct {
{
38
,
"A6"
},
{
39
,
"DNAME"
},
{
41
,
"OPT"
},
{
43
,
"DS"
},
{
46
,
"RRSIG"
},
{
48
,
"DNSKEY"
},
{
249
,
"TKEY"
},
{
250
,
"TSIG"
},
...
...
@@ -916,12 +918,19 @@ void cache_reload(void)
struct
name_list
*
nl
;
struct
cname
*
a
;
struct
interface_name
*
intr
;
#ifdef HAVE_DNSSEC
struct
dnskey
*
key
;
#endif
cache_inserted
=
cache_live_freed
=
0
;
for
(
i
=
0
;
i
<
hash_size
;
i
++
)
for
(
cache
=
hash_table
[
i
],
up
=
&
hash_table
[
i
];
cache
;
cache
=
tmp
)
{
#ifdef HAVE_DNSSEC
if
(
cache
->
flags
&
(
F_DNSKEY
|
F_DS
))
blockdata_free
(
cache
->
addr
.
key
.
keydata
);
#endif
tmp
=
cache
->
hash_next
;
if
(
cache
->
flags
&
(
F_HOSTS
|
F_CONFIG
))
{
...
...
@@ -948,13 +957,27 @@ void cache_reload(void)
if
(
hostname_isequal
(
a
->
target
,
intr
->
name
)
&&
((
cache
=
whine_malloc
(
sizeof
(
struct
crec
)))))
{
cache
->
flags
=
F_FORWARD
|
F_NAMEP
|
F_CNAME
|
F_IMMORTAL
|
F_CONFIG
;
cache
->
flags
=
F_FORWARD
|
F_NAMEP
|
F_CNAME
|
F_IMMORTAL
|
F_CONFIG
|
F_DNSSECOK
;
cache
->
name
.
namep
=
a
->
alias
;
cache
->
addr
.
cname
.
target
.
int_name
=
intr
;
cache
->
addr
.
cname
.
uid
=
-
1
;
cache_hash
(
cache
);
add_hosts_cname
(
cache
);
/* handle chains */
}
#ifdef HAVE_DNSSEC
for
(
key
=
daemon
->
dnskeys
;
key
;
key
=
key
->
next
)
if
((
cache
=
whine_malloc
(
sizeof
(
struct
crec
)))
&&
(
cache
->
addr
.
key
.
keydata
=
blockdata_alloc
(
key
->
key
,
key
->
keylen
)))
{
cache
->
flags
=
F_FORWARD
|
F_IMMORTAL
|
F_DNSKEY
|
F_CONFIG
|
F_NAMEP
;
cache
->
name
.
namep
=
key
->
name
;
cache
->
uid
=
key
->
keylen
;
cache
->
addr
.
key
.
algo
=
key
->
algo
;
cache
->
addr
.
key
.
keytag
=
dnskey_keytag
(
key
->
algo
,
key
->
flags
,
(
unsigned
char
*
)
key
->
key
,
key
->
keylen
);
cache_hash
(
cache
);
}
#endif
/* borrow the packet buffer for a temporary by-address hash */
memset
(
daemon
->
packet
,
0
,
daemon
->
packet_buff_sz
);
...
...
@@ -1197,16 +1220,13 @@ void dump_cache(time_t now)
for
(
i
=
0
;
i
<
hash_size
;
i
++
)
for
(
cache
=
hash_table
[
i
];
cache
;
cache
=
cache
->
hash_next
)
{
char
*
a
,
*
p
=
daemon
->
namebuff
;
p
+=
sprintf
(
p
,
"%-40.40s "
,
cache_get_name
(
cache
));
if
((
cache
->
flags
&
F_NEG
)
&&
(
cache
->
flags
&
F_FORWARD
))
a
=
""
;
else
if
(
cache
->
flags
&
F_CNAME
)
{
a
=
""
;
if
(
!
is_outdated_cname_pointer
(
cache
))
a
=
cache_get_cname_target
(
cache
);
}
char
*
a
=
daemon
->
addrbuff
,
*
p
=
daemon
->
namebuff
,
*
n
=
cache_get_name
(
cache
);
*
a
=
0
;
if
(
strlen
(
n
)
==
0
)
n
=
"<Root>"
;
p
+=
sprintf
(
p
,
"%-40.40s "
,
n
);
if
((
cache
->
flags
&
F_CNAME
)
&&
!
is_outdated_cname_pointer
(
cache
))
a
=
cache_get_cname_target
(
cache
);
#ifdef HAVE_DNSSEC
else
if
(
cache
->
flags
&
F_DNSKEY
)
{
...
...
@@ -1216,11 +1236,11 @@ void dump_cache(time_t now)
else
if
(
cache
->
flags
&
F_DS
)
{
a
=
daemon
->
addrbuff
;
sprintf
(
a
,
"%5u %3u %3u
%u
"
,
cache
->
addr
.
key
.
keytag
,
cache
->
addr
.
key
.
algo
,
cache
->
addr
.
key
.
digest
,
cache
->
uid
);
sprintf
(
a
,
"%5u %3u %3u"
,
cache
->
addr
.
key
.
keytag
,
cache
->
addr
.
key
.
algo
,
cache
->
addr
.
key
.
digest
);
}
#endif
else
else
if
(
!
(
cache
->
flags
&
F_NEG
)
||
!
(
cache
->
flags
&
F_FORWARD
))
{
a
=
daemon
->
addrbuff
;
if
(
cache
->
flags
&
F_IPV4
)
...
...
@@ -1291,13 +1311,20 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
if
(
addr
)
{
if
(
flags
&
F_KEYTAG
)
sprintf
(
daemon
->
addrbuff
,
arg
,
addr
->
addr
.
keytag
);
else
{
#ifdef HAVE_IPV6
inet_ntop
(
flags
&
F_IPV4
?
AF_INET
:
AF_INET6
,
addr
,
daemon
->
addrbuff
,
ADDRSTRLEN
);
inet_ntop
(
flags
&
F_IPV4
?
AF_INET
:
AF_INET6
,
addr
,
daemon
->
addrbuff
,
ADDRSTRLEN
);
#else
strncpy
(
daemon
->
addrbuff
,
inet_ntoa
(
addr
->
addr
.
addr4
),
ADDRSTRLEN
);
strncpy
(
daemon
->
addrbuff
,
inet_ntoa
(
addr
->
addr
.
addr4
),
ADDRSTRLEN
);
#endif
}
}
else
dest
=
arg
;
if
(
flags
&
F_REVERSE
)
{
...
...
@@ -1339,6 +1366,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source
=
arg
;
else
if
(
flags
&
F_UPSTREAM
)
source
=
"reply"
;
else
if
(
flags
&
F_SECSTAT
)
source
=
"validation"
;
else
if
(
flags
&
F_AUTH
)
source
=
"auth"
;
else
if
(
flags
&
F_SERVER
)
...
...
@@ -1351,6 +1380,11 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
source
=
arg
;
verb
=
"from"
;
}
else
if
(
flags
&
F_DNSSEC
)
{
source
=
arg
;
verb
=
"to"
;
}
else
source
=
"cached"
;
...
...
@@ -1422,6 +1456,21 @@ void blockdata_free(struct blockdata *blocks)
keyblock_free
=
blocks
;
}
}
void
blockdata_retrieve
(
struct
blockdata
*
block
,
size_t
len
,
void
*
data
)
{
size_t
blen
;
struct
blockdata
*
b
;
for
(
b
=
block
;
len
>
0
&&
b
;
b
=
b
->
next
)
{
blen
=
len
>
KEYBLOCK_LEN
?
KEYBLOCK_LEN
:
len
;
memcpy
(
data
,
b
->
key
,
blen
);
data
+=
blen
;
len
-=
blen
;
}
}
#endif
src/config.h
View file @
0fc2f313
...
...
@@ -139,8 +139,8 @@ RESOLVFILE
/* #define HAVE_DBUS */
/* #define HAVE_IDN */
/* #define HAVE_CONNTRACK */
#define HAVE_DNSSEC
#define HAVE_OPENSSL
#define HAVE_DNSSEC
#define HAVE_OPENSSL
/* Default locations for important system files. */
...
...
@@ -385,7 +385,12 @@ static char *compile_opts =
#ifndef HAVE_AUTH
"no-"
#endif
"auth"
;
"auth "
#ifndef HAVE_DNSSEC
"no-"
#endif
"DNSSEC"
;
#endif
...
...
src/dnsmasq.h
View file @
0fc2f313
...
...
@@ -242,6 +242,7 @@ struct all_addr {
#ifdef HAVE_IPV6
struct
in6_addr
addr6
;
#endif
unsigned
int
keytag
;
}
addr
;
};
...
...
@@ -286,6 +287,12 @@ struct cname {
struct
cname
*
next
;
};
struct
dnskey
{
char
*
name
,
*
key
;
int
keylen
,
algo
,
flags
;
struct
dnskey
*
next
;
};
#define ADDRLIST_LITERAL 1
#define ADDRLIST_IPV6 2
...
...
@@ -360,7 +367,7 @@ struct crec {
}
key
;
}
addr
;
time_t
ttd
;
/* time to die */
/* used as keylen if
F_DS or
F_DNSKEY, index to source for F_HOSTS */
/* used as keylen ifF_DNSKEY, index to source for F_HOSTS */
int
uid
;
unsigned
short
flags
;
union
{
...
...
@@ -395,6 +402,9 @@ struct crec {
#define F_QUERY (1u<<19)
#define F_NOERR (1u<<20)
#define F_AUTH (1u<<21)
#define F_DNSSEC (1u<<22)
#define F_KEYTAG (1u<<23)
#define F_SECSTAT (1u<<24)
/* composites */
#define F_TYPE (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS)
/* Only one may be set */
...
...
@@ -896,6 +906,9 @@ extern struct daemon {
#ifdef OPTION6_PREFIX_CLASS
struct
prefix_class
*
prefix_classes
;
#endif
#ifdef HAVE_DNSSEC
struct
dnskey
*
dnskeys
;
#endif
/* globally used stuff for DNS */
char
*
packet
;
/* packet buffer */
...
...
@@ -977,6 +990,7 @@ struct crec *cache_enumerate(int init);
#ifdef HAVE_DNSSEC
struct
blockdata
*
blockdata_alloc
(
char
*
data
,
size_t
len
);
size_t
blockdata_walk
(
struct
blockdata
**
key
,
unsigned
char
**
p
,
size_t
cnt
);
void
blockdata_retrieve
(
struct
blockdata
*
block
,
size_t
len
,
void
*
data
);
void
blockdata_free
(
struct
blockdata
*
blocks
);
#endif
...
...
@@ -1000,7 +1014,7 @@ size_t setup_reply(struct dns_header *header, size_t qlen,
unsigned
long
local_ttl
);
int
extract_addresses
(
struct
dns_header
*
header
,
size_t
qlen
,
char
*
namebuff
,
time_t
now
,
char
**
ipsets
,
int
is_sign
,
int
checkrebind
,
int
checking_disabled
);
int
no_cache
,
int
secure
);
size_t
answer_request
(
struct
dns_header
*
header
,
char
*
limit
,
size_t
qlen
,
struct
in_addr
local_addr
,
struct
in_addr
local_netmask
,
time_t
now
);
int
check_for_bogus_wildcard
(
struct
dns_header
*
header
,
size_t
qlen
,
char
*
name
,
...
...
@@ -1034,11 +1048,13 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif
/* dnssec.c */
size_t
dnssec_generate_query
(
struct
dns_header
*
header
,
char
*
name
,
int
class
,
int
type
);
size_t
dnssec_generate_query
(
struct
dns_header
*
header
,
char
*
name
,
int
class
,
int
type
,
union
mysockaddr
*
addr
);
int
dnssec_validate_by_ds
(
time_t
now
,
struct
dns_header
*
header
,
size_t
n
,
char
*
name
,
char
*
keyname
,
int
class
);
int
dnssec_validate_ds
(
time_t
now
,
struct
dns_header
*
header
,
size_t
plen
,
char
*
name
,
char
*
keyname
,
int
class
);
int
validate_rrset
(
time_t
now
,
struct
dns_header
*
header
,
size_t
plen
,
int
class
,
int
type
,
char
*
name
,
char
*
keyname
);
int
dnssec_validate_reply
(
struct
dns_header
*
header
,
size_t
plen
,
char
*
name
,
char
*
keyname
,
int
*
class
);
int
validate_rrset
(
time_t
now
,
struct
dns_header
*
header
,
size_t
plen
,
int
class
,
int
type
,
char
*
name
,
char
*
keyname
,
struct
blockdata
*
key
,
int
keylen
,
int
algo
,
int
keytag
);
int
dnssec_validate_reply
(
time_t
now
,
struct
dns_header
*
header
,
size_t
plen
,
char
*
name
,
char
*
keyname
,
int
*
class
);
int
dnskey_keytag
(
int
alg
,
int
flags
,
unsigned
char
*
rdata
,
int
rdlen
);
/* util.c */
void
rand_init
(
void
);
...
...
@@ -1065,6 +1081,9 @@ void prettyprint_time(char *buf, unsigned int t);
int
prettyprint_addr
(
union
mysockaddr
*
addr
,
char
*
buf
);
int
parse_hex
(
char
*
in
,
unsigned
char
*
out
,
int
maxlen
,
unsigned
int
*
wildcard_mask
,
int
*
mac_type
);
#ifdef HAVE_DNSSEC
int
parse_base64
(
char
*
in
,
char
*
out
);
#endif
int
memcmp_masked
(
unsigned
char
*
a
,
unsigned
char
*
b
,
int
len
,
unsigned
int
mask
);
int
expand_buf
(
struct
iovec
*
iov
,
size_t
size
);
...
...
src/dnssec-crypto.h
View file @
0fc2f313
...
...
@@ -17,7 +17,7 @@
#ifndef DNSSEC_CRYPTO_H
#define DNSSEC_CRYPTO_H
struct
key
data
;
struct
block
data
;
/*
* vtable for a signature verification algorithm.
...
...
@@ -49,7 +49,7 @@ typedef struct VerifyAlgCtx VerifyAlgCtx;
typedef
struct
{
int
digest_algo
;
int
(
*
verify
)(
VerifyAlgCtx
*
ctx
,
struct
key
data
*
key
,
unsigned
key_len
);
int
(
*
verify
)(
VerifyAlgCtx
*
ctx
,
struct
block
data
*
key
,
unsigned
key_len
);
}
VerifyAlg
;
struct
VerifyAlgCtx
...
...
@@ -74,9 +74,9 @@ int verifyalg_algonum(VerifyAlgCtx *a);
#define DIGESTALG_SHA512 257
int
digestalg_supported
(
int
algo
);
int
digestalg_begin
(
int
algo
);
void
digestalg_begin
(
int
algo
);
void
digestalg_add_data
(
void
*
data
,
unsigned
len
);
void
digestalg_add_keydata
(
struct
key
data
*
key
,
size_t
len
);
void
digestalg_add_keydata
(
struct
block
data
*
key
,
size_t
len
);
unsigned
char
*
digestalg_final
(
void
);
int
digestalg_len
(
void
);
...
...
src/dnssec-openssl.c
View file @
0fc2f313
...
...
@@ -14,13 +14,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "dnsmasq.h"
#ifdef HAVE_DNSSEC
#include "dnssec-crypto.h"
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/dsa.h>
#include <openssl/err.h>
#include <string.h>
#define POOL_SIZE 1
static
union
_Pool
...
...
@@ -39,20 +42,20 @@ static void print_hex(unsigned char *data, unsigned len)
printf
(
"
\n
"
);
}
static
int
keydata_to_bn
(
BIGNUM
*
ret
,
struct
key
data
**
key_data
,
unsigned
char
**
p
,
unsigned
len
)
static
int
keydata_to_bn
(
BIGNUM
*
ret
,
struct
block
data
**
key_data
,
unsigned
char
**
p
,
unsigned
len
)
{
size_t
cnt
;
BIGNUM
temp
;
BN_init
(
ret
);
cnt
=
key
data_walk
(
key_data
,
p
,
len
);
cnt
=
block
data_walk
(
key_data
,
p
,
len
);
BN_bin2bn
(
*
p
,
cnt
,
ret
);
len
-=
cnt
;
*
p
+=
cnt
;
while
(
len
>
0
)
{
if
(
!
(
cnt
=
key
data_walk
(
key_data
,
p
,
len
)))
if
(
!
(
cnt
=
block
data_walk
(
key_data
,
p
,
len
)))
return
0
;
BN_lshift
(
ret
,
ret
,
cnt
*
8
);
BN_init
(
&
temp
);
...
...
@@ -64,7 +67,7 @@ static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char *
return
1
;
}
static
int
rsasha1_parse_key
(
BIGNUM
*
exp
,
BIGNUM
*
mod
,
struct
key
data
*
key_data
,
unsigned
key_len
)
static
int
rsasha1_parse_key
(
BIGNUM
*
exp
,
BIGNUM
*
mod
,
struct
block
data
*
key_data
,
unsigned
key_len
)
{
unsigned
char
*
p
=
key_data
->
key
;
size_t
exp_len
,
mod_len
;
...
...
@@ -80,7 +83,7 @@ static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data,
keydata_to_bn
(
mod
,
&
key_data
,
&
p
,
mod_len
);
}
static
int
dsasha1_parse_key
(
BIGNUM
*
Q
,
BIGNUM
*
P
,
BIGNUM
*
G
,
BIGNUM
*
Y
,
struct
key
data
*
key_data
,
unsigned
key_len
)
static
int
dsasha1_parse_key
(
BIGNUM
*
Q
,
BIGNUM
*
P
,
BIGNUM
*
G
,
BIGNUM
*
Y
,
struct
block
data
*
key_data
,
unsigned
key_len
)
{
unsigned
char
*
p
=
key_data
->
key
;
int
T
;
...
...
@@ -93,7 +96,7 @@ static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct
keydata_to_bn
(
Y
,
&
key_data
,
&
p
,
64
+
T
*
8
);
}
static
int
rsa_verify
(
VerifyAlgCtx
*
ctx
,
struct
key
data
*
key_data
,
unsigned
key_len
,
int
nid
,
int
dlen
)
static
int
rsa_verify
(
VerifyAlgCtx
*
ctx
,
struct
block
data
*
key_data
,
unsigned
key_len
,
int
nid
,
int
dlen
)
{
int
validated
=
0
;
...
...
@@ -108,27 +111,27 @@ static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_
return
validated
;
}
static
int
rsamd5_verify
(
VerifyAlgCtx
*
ctx
,
struct
key
data
*
key_data
,
unsigned
key_len
)
static
int
rsamd5_verify
(
VerifyAlgCtx
*
ctx
,
struct
block
data
*
key_data
,
unsigned
key_len
)
{
return
rsa_verify
(
ctx
,
key_data
,
key_len
,
NID_md5
,
16
);
}
static
int
rsasha1_verify
(
VerifyAlgCtx
*
ctx
,
struct
key
data
*
key_data
,
unsigned
key_len
)
static
int
rsasha1_verify
(
VerifyAlgCtx
*
ctx
,
struct
block
data
*
key_data
,
unsigned
key_len
)
{
return
rsa_verify
(
ctx
,
key_data
,
key_len
,
NID_sha1
,
20
);
}
static
int
rsasha256_verify
(
VerifyAlgCtx
*
ctx
,
struct
key
data
*
key_data
,
unsigned
key_len
)
static
int
rsasha256_verify
(
VerifyAlgCtx
*
ctx
,
struct
block
data
*
key_data
,
unsigned
key_len
)
{
return
rsa_verify
(
ctx
,
key_data
,
key_len
,
NID_sha256
,
32
);
}
static
int
rsasha512_verify
(
VerifyAlgCtx
*
ctx
,
struct
key
data
*
key_data
,
unsigned
key_len
)
static
int
rsasha512_verify
(
VerifyAlgCtx
*
ctx
,
struct
block
data
*
key_data
,
unsigned
key_len
)
{
return
rsa_verify
(
ctx
,
key_data
,
key_len
,
NID_sha512
,
64
);
}
static
int
dsasha1_verify
(
VerifyAlgCtx
*
ctx
,
struct
key
data
*
key_data
,
unsigned
key_len
)
static
int
dsasha1_verify
(
VerifyAlgCtx
*
ctx
,
struct
block
data
*
key_data
,
unsigned
key_len
)
{
static
unsigned
char
asn1_signature
[]
=
{
...
...
@@ -222,9 +225,6 @@ VerifyAlgCtx* verifyalg_alloc(int algo)
int
i
;
VerifyAlgCtx
*
ret
=
0
;
if
(
!
verifyalg_supported
(
algo
))
return
0
;
if
(
pool_used
==
(
1
<<
POOL_SIZE
)
-
1
)
ret
=
whine_malloc
(
valgctx_size
[
algo
]);
else
...
...
@@ -271,7 +271,7 @@ int digestalg_supported(int algo)
algo
==
DIGESTALG_SHA512
);
}
int
digestalg_begin
(
int
algo
)
void
digestalg_begin
(
int
algo
)
{
EVP_MD_CTX_init
(
&
digctx
);
if
(
algo
==
DIGESTALG_SHA1
)
...
...
@@ -282,9 +282,6 @@ int digestalg_begin(int algo)
EVP_DigestInit_ex
(
&
digctx
,
EVP_sha512
(),
NULL
);
else
if
(
algo
==
DIGESTALG_MD5
)
EVP_DigestInit_ex
(
&
digctx
,
EVP_md5
(),
NULL
);
else
return
0
;
return
1
;
}
int
digestalg_len
()
...
...
@@ -297,12 +294,12 @@ void digestalg_add_data(void *data, unsigned len)
EVP_DigestUpdate
(
&
digctx
,
data
,
len
);
}
void
digestalg_add_keydata
(
struct
key
data
*
key
,
size_t
len
)
void
digestalg_add_keydata
(
struct
block
data
*
key
,
size_t
len
)
{
size_t
cnt
;
unsigned
char
*
p
=
NULL
;
while
(
len
)
{
cnt
=
key
data_walk
(
&
key
,
&
p
,
len
);
cnt
=
block
data_walk
(
&
key
,
&
p
,
len
);
EVP_DigestUpdate
(
&
digctx
,
p
,
cnt
);
p
+=
cnt
;
len
-=
cnt
;
...
...
@@ -316,3 +313,4 @@ unsigned char* digestalg_final(void)
return
digest
;
}
#endif
/* HAVE_DNSSEC */
src/dnssec.c
View file @
0fc2f313
/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
and Copyright (c) 2012-2014 Simon Kelley
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
...
...
@@ -15,11 +16,14 @@
*/
#include "dnsmasq.h"
#ifdef HAVE_DNSSEC
#include "dnssec-crypto.h"
#include <assert.h>
/* Maximum length in octects of a domain name, in wire format */
#define MAXCDNAME 256
#define MAXCDNAME 256
#define MAXRRSET 16
...
...
@@ -28,8 +32,6 @@
#define SERIAL_LT -1
#define SERIAL_GT 1
static
int
dnskey_keytag
(
int
alg
,
unsigned
char
*
rdata
,
int
rdlen
);
/* Implement RFC1982 wrapped compare for 32-bit numbers */
static
int
serial_compare_32
(
unsigned
long
s1
,
unsigned
long
s2
)
{
...
...
@@ -45,36 +47,6 @@ static int serial_compare_32(unsigned long s1, unsigned long s2)
return
SERIAL_UNDEF
;
}
/* Extract a DNS name from wire format, without handling compression. This is
faster than extract_name() and does not require access to the full dns
packet. */
static
int
extract_name_no_compression
(
unsigned
char
*
rr
,
int
maxlen
,
char
*
buf
)
{
unsigned
char
*
start
=
rr
,
*
end
=
rr
+
maxlen
;
int
count
;
while
(
rr
<
end
&&
*
rr
!=
0
)
{
count
=
*
rr
++
;
while
(
count
--
>
0
&&
rr
<
end
)
{
*
buf
=
*
rr
++
;
if
(
!
isascii
(
*
buf
)
||
iscntrl
(
*
buf
)
||
*
buf
==
'.'
)
return
0
;
if
(
*
buf
>=
'A'
&&
*
buf
<=
'Z'
)
*
buf
+=
'a'
-
'A'
;
buf
++
;
}
*
buf
++
=
'.'
;
}
/* Remove trailing dot (if any) */
if
(
rr
!=
start
)
*
(
--
buf
)
=
0
;
if
(
rr
==
end
)
return
0
;
/* Trailing \0 in source data must be consumed */
return
rr
-
start
+
1
;
}
/* process_domain_name() - do operations with domain names in canonicalized wire format.
*
...
...
@@ -419,46 +391,55 @@ typedef struct PendingRRSIGValidation
int
keytag
;
}
PendingRRSIGValidation
;
/* strchrnul - like strchr, but when character is not found, returns a pointer to the terminating \0.
This is an existing C GNU extension, but it's easier to reimplement it,
rather than tweaking with configure. */
static
char
*
my_strchrnul
(
char
*
str
,
char
ch
)
/* Convert from presentation format to wire format, in place.
Also map UC -> LC.
Note that using extract_name to get presentation format
then calling to_wire() removes compression and maps case,
thus generating names in canonical form.
Calling to_wire followed by from_wire is almost an identity,
except that the UC remains mapped to LC.
*/
static
int
to_wire
(
char
*
name
)
{
while
(
*
str
&&
*
str
!=
ch
)
str
++
;
return
str
;
unsigned
char
*
l
,
*
p
,
term
;
int
len
;
for
(
l
=
(
unsigned
char
*
)
name
;
*
l
!=
0
;
l
=
p
)
{
for
(
p
=
l
;
*
p
!=
'.'
&&
*
p
!=
0
;
p
++
)
if
(
*
p
>=
'A'
&&
*
p
<=
'Z'
)
*
p
=
*
p
-
'A'
+
'a'
;
term
=
*
p
;
if
((
len
=
p
-
l
)
!=
0
)
memmove
(
l
+
1
,
l
,
len
);
*
l
=
len
;
p
++
;
if
(
term
==
0
)
*
p
=
0
;
}
return
l
+
1
-
(
unsigned
char
*
)
name
;
}
/*
Convert a domain name to wire format
*/
static
int
convert_domain_to_wire
(
char
*
name
,
unsigned
char
*
out
)
/*
Note: no compression allowed in input.
*/
static
void
from_wire
(
char
*
name
)
{
unsigned
char
len
;
unsigned
char
*
start
=
out
;
char
*
p
;
unsigned
char
*
l
;
int
len
;
do
for
(
l
=
(
unsigned
char
*
)
name
;
*
l
!=
0
;
l
+=
len
+
1
)
{
p
=
my_strchrnul
(
name
,
'.'
);
if
((
len
=
p
-
name
))
{
*
out
++
=
len
;
while
(
len
--
)
{
char
ch
=
*
name
++
;
/* TODO: this will not be required anymore once we
remove all usages of extract_name() from DNSSEC code */
if
(
ch
>=
'A'
&&
ch
<=
'Z'
)
ch
=
ch
-
'A'
+
'a'
;
*
out
++
=
ch
;
}
}
name
=
p
+
1
;
len
=
*
l
;
memmove
(
l
,
l
+
1
,
len
);
l
[
len
]
=
'.'
;
}
while
(
*
p
);
*
out
++
=
'\0'
;
return
out
-
start
;
*
(
l
-
1
)
=
0
;
}
...
...
@@ -498,43 +479,56 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk
return
1
;
}
size_t
dnssec_generate_query
(
struct
dns_header
*
header
,
char
*
name
,
int
class
,
int
type
)
size_t
dnssec_generate_query
(
struct
dns_header
*
header
,
char
*
name
,
int
class
,
int
type
,
union
mysockaddr
*
addr
)
{
unsigned
char
*
p
;
char
types
[
20
];
querystr
(
"dnssec"
,
types
,
type
);
if
(
addr
->
sa
.
sa_family
==
AF_INET
)
log_query
(
F_DNSSEC
|
F_IPV4
,
name
,
(
struct
all_addr
*
)
&
addr
->
in
.
sin_addr
,
types
);
#ifdef HAVE_IPV6
else
log_query
(
F_DNSSEC
|
F_IPV6
,
name
,
(
struct
all_addr
*
)
&
addr
->
in6
.
sin6_addr
,
types
);
#endif
header
->
qdcount
=
htons
(
1
);
header
->
ancount
=
htons
(
0
);
header
->
nscount
=
htons
(
0
);
header
->
arcount
=
htons
(
0
);
header
->
hb3
=
HB3_RD
;
header
->
hb3
=
HB3_RD
;
SET_OPCODE
(
header
,
QUERY
);
header
->
hb4
=
0
;
header
->
hb4
=
HB4_CD
;
/* ID filled in later */
p
=
(
unsigned
char
*
)(
header
+
1
);
p
=
do_rfc1035_name
(
p
,
name
);
*
p
++
=
0
;
PUTSHORT
(
type
,
p
);
PUTSHORT
(
class
,
p
);
return
add_do_bit
(
header
,
p
-
(
unsigned
char
*
)
header
,
((
char
*
)
header
)
+
PACKETSZ
);
}
/* The DNS packet is expected to contain the answer to a DNSKEY query
/* The DNS packet is expected to contain the answer to a DNSKEY query.
Leave name of qury in name.
Put all DNSKEYs in the answer which are valid into the cache.
return codes:
STAT_INSECURE bad packet, no DNSKEYs in reply.
STAT_SECURE At least one valid DNSKEY found and in cache.
STAT_BOGUS At least one DNSKEY found, which fails validation.
STAT_NEED_DS DS records to validate a key not found, name in namebuff
STAT_BOGUS No DNSKEYs found, which can be validated with DS,
or self-sign for DNSKEY RRset is not valid.
STAT_NEED_DS DS records to validate a key not found, name in keyname
*/
int
dnssec_validate_by_ds
(
time_t
now
,
struct
dns_header
*
header
,
size_t
plen
,
char
*
name
,
char
*
keyname
,
int
class
)
{
unsigned
char
*
p
;
unsigned
char
*
p
save
,
*
p
=
(
unsigned
char
*
)(
header
+
1
)
;
struct
crec
*
crecp
,
*
recp1
;
int
j
,
qtype
,
qclass
,
ttl
,
rdlen
,
flags
,
protocol
,
algo
,
gotone
;
int
rc
,
j
,
qtype
,
qclass
,
ttl
,
rdlen
,
flags
,
algo
,
valid
,
keytag
;
struct
blockdata
*
key
;
if
(
ntohs
(
header
->
qdcount
)
!=
1
)
...
...
@@ -546,15 +540,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETSHORT
(
qtype
,
p
);
GETSHORT
(
qclass
,
p
);
if
(
qtype
!=
T_DNSKEY
||
qclass
!=
class
)
if
(
qtype
!=
T_DNSKEY
||
qclass
!=
class
||
ntohs
(
header
->
ancount
)
==
0
)
return
STAT_INSECURE
;
/* See if we have cached a DS record which validates this key */
if
(
!
(
crecp
=
cache_find_by_name
(
NULL
,
name
,
now
,
F_DS
)))
{
strcpy
(
keyname
,
name
);
return
STAT_NEED_DS
;
}
cache_start_insert
();
for
(
gotone
=
0
,
j
=
ntohs
(
header
->
ancount
);
j
!=
0
;
j
--
)
/* NOTE, we need to find ONE DNSKEY which matches the DS */
for
(
valid
=
0
,
j
=
ntohs
(
header
->
ancount
);
j
!=
0
;
j
--
)
{
/* Ensure we have type, class TTL and length */
if
(
!
extract_name
(
header
,
plen
,
&
p
,
name
,
1
,
10
))
if
(
!
(
rc
=
extract_name
(
header
,
plen
,
&
p
,
name
,
0
,
10
)
))
return
STAT_INSECURE
;
/* bad packet */
GETSHORT
(
qtype
,
p
);
...
...
@@ -562,64 +564,89 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETLONG
(
ttl
,
p
);
GETSHORT
(
rdlen
,
p
);
if
(
qclass
!=
class
||
qtype
!=
T_DNSKEY
||
r
dlen
<
4
)
if
(
qclass
!=
class
||
qtype
!=
T_DNSKEY
||
r
c
==
2
)
{
/* skip all records other than DNSKEY */
p
+=
rdlen
;
continue
;
if
(
ADD_RDLEN
(
header
,
p
,
plen
,
rdlen
))
continue
;
return
STAT_INSECURE
;
/* bad packet */
}
crecp
=
cache_find_by_name
(
NULL
,
name
,
now
,
F_DS
);
if
(
!
CHECK_LEN
(
header
,
p
,
plen
,
rdlen
)
||
rdlen
<
4
)
return
STAT_INSECURE
;
/* bad packet */
psave
=
p
;
/* length at least covers flags, protocol and algo now. */
GETSHORT
(
flags
,
p
);
protocol
=
*
p
++
;
if
(
*
p
++
!=
3
)
return
STAT_INSECURE
;
algo
=
*
p
++
;
/* See if we have cached a DS record which validates this key */
for
(
recp1
=
crecp
;
recp1
;
recp1
=
cache_find_by_name
(
recp1
,
name
,
now
,
F_DS
))
if
(
recp1
->
addr
.
key
.
algo
==
algo
&&
is_supported_digest
(
recp1
->
addr
.
key
.
digest
))
break
;
keytag
=
dnskey_keytag
(
algo
,
flags
,
p
,
rdlen
-
4
);
/*
DS record needed to validate key is missing, return name of DS in namebuff */
if
(
!
recp1
)
return
STAT_NEED_DS
;
else
/*
Put the key into the cache. Note that if the validation fails, we won't
call cache_end_insert() and this will never be committed. */
if
((
key
=
blockdata_alloc
((
char
*
)
p
,
rdlen
-
4
))
&&
(
recp1
=
cache_insert
(
name
,
NULL
,
now
,
ttl
,
F_FORWARD
|
F_DNSKEY
)))
{
int
valid
=
1
;
/* calculate digest of canonicalised DNSKEY data using digest in (recp1->addr.key.digest)
and see if it equals digest stored in recp1
*/
if
(
!
valid
)
return
STAT_BOGUS
;
recp1
->
uid
=
rdlen
-
4
;
recp1
->
addr
.
key
.
keydata
=
key
;
recp1
->
addr
.
key
.
algo
=
algo
;
recp1
->
addr
.
key
.
keytag
=
keytag
;
}
if
((
key
=
blockdata_alloc
((
char
*
)
p
,
rdlen
)))
{
/* We've proved that the KEY is OK, store it in the cache */
if
((
crecp
=
cache_insert
(
name
,
NULL
,
now
,
ttl
,
F_FORWARD
|
F_DNSKEY
)))
{
crecp
->
uid
=
rdlen
;
crecp
->
addr
.
key
.
keydata
=
key
;
crecp
->
addr
.
key
.
algo
=
algo
;
crecp
->
addr
.
key
.
keytag
=
dnskey_keytag
(
algo
,
(
char
*
)
p
,
rdlen
);
gotone
=
1
;
}
}
p
=
psave
;
if
(
!
ADD_RDLEN
(
header
,
p
,
plen
,
rdlen
))
return
STAT_INSECURE
;
/* bad packet */
/* Already determined that message is OK. Just loop stuffing cache */
if
(
valid
||
!
key
)
continue
;
for
(
recp1
=
crecp
;
recp1
;
recp1
=
cache_find_by_name
(
recp1
,
name
,
now
,
F_DS
))
if
(
recp1
->
addr
.
key
.
algo
==
algo
&&
recp1
->
addr
.
key
.
keytag
==
keytag
&&
(
flags
&
0x100
)
&&
/* zone key flag */
digestalg_supported
(
recp1
->
addr
.
key
.
digest
))
{
int
wire_len
=
to_wire
(
name
);
digestalg_begin
(
recp1
->
addr
.
key
.
digest
);
digestalg_add_data
(
name
,
wire_len
);
digestalg_add_data
((
char
*
)
psave
,
rdlen
);
from_wire
(
name
);
/* TODO fragented digest */
if
(
memcmp
(
digestalg_final
(),
recp1
->
addr
.
key
.
keydata
->
key
,
digestalg_len
())
==
0
&&
validate_rrset
(
now
,
header
,
plen
,
class
,
T_DNSKEY
,
name
,
keyname
,
key
,
rdlen
-
4
,
algo
,
keytag
))
{
struct
all_addr
a
;
valid
=
1
;
a
.
addr
.
keytag
=
keytag
;
log_query
(
F_KEYTAG
|
F_UPSTREAM
,
name
,
&
a
,
"DNSKEY keytag %u"
);
break
;
}
}
}
cache_end_insert
();
return
gotone
?
STAT_SECURE
:
STAT_INSECURE
;
if
(
valid
)
{
/* commit cache insert. */
cache_end_insert
();
return
STAT_SECURE
;
}
log_query
(
F_UPSTREAM
,
name
,
NULL
,
"BOGUS DNSKEY"
);
return
STAT_BOGUS
;
}
/* The DNS packet is expected to contain the answer to a DS query
Leave name of DS query in name.
Put all DSs in the answer which are valid into the cache.
return codes:
STAT_INSECURE bad packet, no D
NSKEYs
in reply.
STAT_INSECURE bad packet, no D
S
in reply.
STAT_SECURE At least one valid DS found and in cache.
STAT_BOGUS At least one DS found, which fails validation.
STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
...
...
@@ -627,8 +654,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
int
dnssec_validate_ds
(
time_t
now
,
struct
dns_header
*
header
,
size_t
plen
,
char
*
name
,
char
*
keyname
,
int
class
)
{
unsigned
char
*
p
=
(
unsigned
char
*
)(
header
+
1
);
struct
crec
*
crecp
,
*
recp1
;
unsigned
char
*
p
save
,
*
p
=
(
unsigned
char
*
)(
header
+
1
);
struct
crec
*
crecp
;
int
qtype
,
qclass
,
val
,
j
,
gotone
;
struct
blockdata
*
key
;
...
...
@@ -641,10 +668,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETSHORT
(
qtype
,
p
);
GETSHORT
(
qclass
,
p
);
if
(
qtype
!=
T_DS
||
qclass
!=
class
)
if
(
qtype
!=
T_DS
||
qclass
!=
class
||
ntohs
(
header
->
ancount
)
==
0
)
return
STAT_INSECURE
;
val
=
validate_rrset
(
header
,
plen
,
class
,
T_DS
,
name
,
keyname
);
val
=
validate_rrset
(
now
,
header
,
plen
,
class
,
T_DS
,
name
,
keyname
,
NULL
,
0
,
0
,
0
);
if
(
val
==
STAT_BOGUS
)
log_query
(
F_UPSTREAM
,
name
,
NULL
,
"BOGUS DS"
);
/* failed to validate or missing key. */
if
(
val
!=
STAT_SECURE
)
...
...
@@ -654,7 +684,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
for
(
gotone
=
0
,
j
=
ntohs
(
header
->
ancount
);
j
!=
0
;
j
--
)
{
int
ttl
,
rdlen
,
rc
,
algo
;
int
ttl
,
rdlen
,
rc
,
algo
,
digest
,
keytag
;
/* Ensure we have type, class TTL and length */
if
(
!
(
rc
=
extract_name
(
header
,
plen
,
&
p
,
name
,
0
,
10
)))
...
...
@@ -666,32 +696,42 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETSHORT
(
rdlen
,
p
);
/* check type, class and name, skip if not in DS rrset */
if
(
qclass
!=
class
||
qtype
!=
T_DS
||
rc
==
2
)
{
p
+=
rdlen
;
continue
;
}
if
((
key
=
blockdata_alloc
((
char
*
)
p
,
rdlen
)))
if
(
qclass
==
class
&&
qtype
==
T_DS
&&
rc
==
1
)
{
if
(
!
CHECK_LEN
(
header
,
p
,
plen
,
rdlen
)
||
rdlen
<
4
)
return
STAT_INSECURE
;
/* bad packet */
psave
=
p
;
GETSHORT
(
keytag
,
p
);
algo
=
*
p
++
;
digest
=
*
p
++
;
/* We've proved that the DS is OK, store it in the cache */
if
((
crecp
=
cache_insert
(
name
,
NULL
,
now
,
ttl
,
F_FORWARD
|
F_DS
)))
if
((
key
=
blockdata_alloc
((
char
*
)
p
,
rdlen
-
4
))
&&
(
crecp
=
cache_insert
(
name
,
NULL
,
now
,
ttl
,
F_FORWARD
|
F_DS
)))
{
crecp
->
uid
=
rdlen
;
struct
all_addr
a
;
a
.
addr
.
keytag
=
keytag
;
log_query
(
F_KEYTAG
|
F_UPSTREAM
,
name
,
&
a
,
"DS keytag %u"
);
crecp
->
addr
.
key
.
digest
=
digest
;
crecp
->
addr
.
key
.
keydata
=
key
;
crecp
->
addr
.
key
.
algo
=
algo
;
crecp
->
addr
.
key
.
keytag
=
dnskey_keytag
(
algo
,
(
char
*
)
p
,
rdlen
);
gotone
=
1
;
crecp
->
addr
.
key
.
keytag
=
keytag
;
}
else
return
STAT_INSECURE
;
/* cache problem */
p
=
psave
;
}
if
(
!
ADD_RDLEN
(
header
,
p
,
plen
,
rdlen
))
return
STAT_INSECURE
;
/* bad packet */
}
cache_end_insert
();
return
gotone
?
STAT_SECURE
:
STAT_INSECURE
;
return
STAT_SECURE
;
}
...
...
@@ -702,528 +742,249 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
STAT_INSECURE can't validate (no RRSIG, bad packet).
STAT_BOGUS signature is wrong.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
otherwise find the key in the cache.
*/
int
validate_rrset
(
time_t
now
,
struct
dns_header
*
header
,
size_t
plen
,
int
class
,
int
type
,
char
*
name
,
char
*
keyname
)
int
validate_rrset
(
time_t
now
,
struct
dns_header
*
header
,
size_t
plen
,
int
class
,
int
type
,
char
*
name
,
char
*
keyname
,
struct
blockdata
*
key
,
int
keylen
,
int
algo_in
,
int
keytag_in
)
{
unsigned
char
*
p
,
*
psav
,
*
sig
;
int
rrsetidx
,
res
,
sigttl
,
sig_data_
len
,
j
;
struct
crec
*
crecp
;
void
*
rrset
[
MAXRRSET
];
/* TODO: max RRset size? */
unsigned
char
*
p
;
int
rrsetidx
,
sigidx
,
res
,
rd
len
,
j
;
struct
crec
*
crecp
=
NULL
;
void
*
rrset
[
MAXRRSET
]
,
*
sigs
[
MAXRRSET
]
;
/* TODO: max RRset size? */
int
type_covered
,
algo
,
labels
,
orig_ttl
,
sig_expiration
,
sig_inception
,
key_tag
;
if
(
!
(
p
=
skip_questions
(
header
,
plen
)))
return
STAT_INSECURE
;
/* look for an RRSIG record for this RRset and get pointers to each record */
for
(
rrsetidx
=
0
,
sig
=
NULL
,
j
=
ntohs
(
header
->
ancount
)
+
ntohs
(
header
->
nscount
);
for
(
rrsetidx
=
0
,
sig
idx
=
0
,
j
=
ntohs
(
header
->
ancount
)
+
ntohs
(
header
->
nscount
);
j
!=
0
;
j
--
)
{
unsigned
char
*
pstart
=
p
;
int
stype
,
sclass
,
sttl
,
rdlen
;
unsigned
char
*
pstart
;
int
stype
,
sclass
,
sttl
;
if
(
!
(
res
=
extract_name
(
header
,
plen
,
&
p
,
name
,
0
,
10
)))
return
STAT_INSECURE
;
/* bad packet */
pstart
=
p
;
GETSHORT
(
stype
,
p
);
GETSHORT
(
sclass
,
p
);
GETLONG
(
sttl
,
p
);
GETSHORT
(
rdlen
,
p
);
(
void
)
sttl
;
if
(
!
CHECK_LEN
(
header
,
p
,
plen
,
rdlen
))
return
STAT_INSECURE
;
/* bad packet */
if
(
res
==
2
||
htons
(
stype
)
!=
T_RRSIG
||
htons
(
sclass
)
!=
class
)
continue
;
if
(
htons
(
stype
)
==
type
)
{
rrset
[
rrsetidx
++
]
=
pstart
;
if
(
rrsetidx
==
MAXRRSET
)
return
STAT_INSECURE
;
/* RRSET too big TODO */
}
if
(
htons
(
stype
)
==
T_RRSIG
)
if
(
res
==
1
&&
sclass
==
class
)
{
/* name matches, RRSIG for correct class */
/* enough data? */
if
(
rdlen
<
18
)
return
STAT_INSECURE
;
GETSHORT
(
type_covered
,
p
);
algo
=
*
p
++
;
labels
=
*
p
++
;
GETLONG
(
orig_ttl
,
p
);
GETLONG
(
sig_expiration
,
p
);
GETLONG
(
sig_inception
,
p
);
GETSHORT
(
key_tag
,
p
);
if
(
type_covered
!=
type
||
!
check_date_range
(
sig_inception
,
sig_expiration
))
if
(
stype
==
type
)
{
/* covers wrong type or out of date - skip */
p
=
psav
;
if
(
!
ADD_RDLEN
(
header
,
p
,
plen
,
rdlen
))
return
STAT_INSECURE
;
continue
;
rrset
[
rrsetidx
++
]
=
pstart
;
if
(
rrsetidx
==
MAXRRSET
)
return
STAT_INSECURE
;
/* RRSET too big TODO */
}
if
(
!
extract_name
(
header
,
plen
,
&
p
,
keyname
,
1
,
0
))
return
STAT_INSECURE
;
/* OK, we have the signature record, see if the
relevant DNSKEY is in the cache. */
for
(
crecp
=
cache_find_by_name
(
NULL
,
keyname
,
now
,
F_DNSKEY
);
crecp
;
crecp
=
cache_find_by_name
(
crecp
,
keyname
,
now
,
F_DNSKEY
))
if
(
crecp
->
addr
.
key
.
algo
==
algo
&&
crecp
->
addr
.
key
.
keytag
==
key_tag
)
break
;
/* No, abort for now whilst we get it */
if
(
!
crecp
)
return
STAT_NEED_KEY
;
/* Save point to signature data */
sig
=
p
;
sig_data_len
=
rdlen
-
(
p
-
psav
);
sigttl
=
sttl
;
/* next record */
p
=
psav
;
if
(
!
ADD_RDLEN
(
header
,
p
,
plen
,
rdlen
))
return
STAT_INSECURE
;
}
if
(
stype
==
T_RRSIG
)
{
sigs
[
sigidx
++
]
=
pstart
;
if
(
sigidx
==
MAXRRSET
)
return
STAT_INSECURE
;
/* RRSET too big TODO */
}
}
if
(
!
ADD_RDLEN
(
header
,
p
,
plen
,
rdlen
))
return
STAT_INSECURE
;
}
/*
Didn't find RRSIG or RRset is empty
*/
if
(
!
sig
||
rrset
idx
==
0
)
/*
RRset empty, no RRSIGs
*/
if
(
rrsetidx
==
0
||
sig
idx
==
0
)
return
STAT_INSECURE
;
/* OK, we have an RRSIG and an RRset and we have a the DNSKEY that validates them. */
/* Sort RRset records in canonical order. */
rrset_canonical_order_ctx
.
header
=
header
;
rrset_canonical_order_ctx
.
pktlen
=
plen
;
qsort
(
rrset
,
rrsetidx
,
sizeof
(
void
*
),
rrset_canonical_order
);
/* Now initialize the signature verification algorithm and process the whole
RRset */
VerifyAlgCtx
*
alg
=
verifyalg_alloc
(
algo
);
if
(
!
alg
)
return
STAT_INSECURE
;
alg
->
sig
=
sig
;
alg
->
siglen
=
sig_data_len
;
u16
ntype
=
htons
(
type
);
u16
nclass
=
htons
(
class
);
u32
nsigttl
=
htonl
(
sigttl
);
/* TODO: we shouldn't need to convert this to wire here. Best solution would be:
- Use process_name() instead of extract_name() everywhere in dnssec code
- Convert from wire format to representation format only for querying/storing cache
*/
unsigned
char
owner_wire
[
MAXCDNAME
];
int
owner_wire_len
=
convert_domain_to_wire
(
name
,
owner_wire
);
digestalg_begin
(
alg
->
vtbl
->
digest_algo
);
digestalg_add_data
(
sigrdata
,
18
+
signer_name_rdlen
);
for
(
i
=
0
;
i
<
rrsetidx
;
++
i
)
/* Now try all the sigs to try and find one which validates */
for
(
j
=
0
;
j
<
sigidx
;
j
++
)
{
p
=
(
unsigned
char
*
)(
rrset
[
i
]);
unsigned
char
*
psav
;
int
i
,
wire_len
;
VerifyAlgCtx
*
alg
;
u16
ntype
,
nclass
;
u32
nsigttl
;
p
=
sigs
[
j
]
+
8
;
/* skip type, class and ttl */
digestalg_add_data
(
owner_wire
,
owner_wire_len
);
digestalg_add_data
(
&
ntype
,
2
);
digestalg_add_data
(
&
nclass
,
2
);
digestalg_add_data
(
&
nsigttl
,
4
);
p
+=
8
;
if
(
!
digestalg_add_rdata
(
ntohs
(
sigtype
),
header
,
pktlen
,
p
))
return
0
;
}
int
digest_len
=
digestalg_len
();
memcpy
(
alg
->
digest
,
digestalg_final
(),
digest_len
);
if
(
alg
->
vtbl
->
verify
(
alg
,
crecp
->
addr
.
key
.
keydata
,
crecp_uid
))
return
STAT_SECURE
;
return
STAT_INSECURE
;
}
#if 0
static int begin_rrsig_validation(struct dns_header *header, size_t pktlen,
unsigned char *reply, int count, char *owner,
int sigclass, int sigrdlen, unsigned char *sig,
PendingRRSIGValidation *out)
{
int i, res;
int sigtype, sigalg, siglbl;
unsigned char *sigrdata = sig;
unsigned long sigttl, date_end, date_start;
unsigned char* p = reply;
char* signer_name = daemon->namebuff;
int signer_name_rdlen;
int keytag;
void *rrset[16]; /* TODO: max RRset size? */
int rrsetidx = 0;
if (sigrdlen < 18)
return 0;
GETSHORT(sigtype, sig);
sigalg = *sig++;
siglbl = *sig++;
GETLONG(sigttl, sig);
GETLONG(date_end, sig);
GETLONG(date_start, sig);
GETSHORT(keytag, sig);
sigrdlen -= 18;
if (!verifyalg_supported(sigalg))
{
printf("ERROR: RRSIG algorithm not supported: %d\n", sigalg);
return 0;
}
if (!check_date_range(date_start, date_end))
{
printf("ERROR: RRSIG outside date range\n");
return 0;
}
/* Iterate within the answer and find the RRsets matching the current RRsig */
for (i = 0; i < count; ++i)
{
int qtype, qclass, rdlen;
if (!(res = extract_name(header, pktlen, &p, owner, 0, 10)))
return 0;
rrset[rrsetidx] = p;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
p += 4; /* skip ttl */
GETSHORT
(
rdlen
,
p
);
if (res == 1 && qtype == sigtype && qclass == sigclass)
{
++rrsetidx;
if (rrsetidx == countof(rrset))
{
/* Internal buffer too small */
printf("internal buffer too small for this RRset\n");
return 0;
}
}
p += rdlen;
}
/* Sort RRset records in canonical order. */
rrset_canonical_order_ctx.header = header;
rrset_canonical_order_ctx.pktlen = pktlen;
qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
/* Skip through the signer name; we don't extract it right now because
we don't want to overwrite the single daemon->namebuff which contains
the owner name. We'll get to this later. */
if (!(p = skip_name(sig, header, pktlen, 0)))
return 0;
signer_name_rdlen = p - sig;
sig = p; sigrdlen -= signer_name_rdlen;
/* Now initialize the signature verification algorithm and process the whole
RRset */
VerifyAlgCtx *alg = verifyalg_alloc(sigalg);
if (!alg)
return 0;
alg->sig = sig;
alg->siglen = sigrdlen;
sigtype = htons(sigtype);
sigclass = htons(sigclass);
sigttl = htonl(sigttl);
/* TODO: we shouldn't need to convert this to wire here. Best solution would be:
- Use process_name() instead of extract_name() everywhere in dnssec code
- Convert from wire format to representation format only for querying/storing cache
*/
unsigned char owner_wire[MAXCDNAME];
int owner_wire_len = convert_domain_to_wire(owner, owner_wire);
digestalg_begin(alg->vtbl->digest_algo);
digestalg_add_data(sigrdata, 18+signer_name_rdlen);
for (i = 0; i < rrsetidx; ++i)
{
p = (unsigned char*)(rrset[i]);
digestalg_add_data(owner_wire, owner_wire_len);
digestalg_add_data(&sigtype, 2);
digestalg_add_data(&sigclass, 2);
digestalg_add_data(&sigttl, 4);
if
(
rdlen
<
18
)
return
STAT_INSECURE
;
/* bad packet */
psav
=
p
;
GETSHORT
(
type_covered
,
p
);
algo
=
*
p
++
;
labels
=
*
p
++
;
GETLONG
(
orig_ttl
,
p
);
GETLONG
(
sig_expiration
,
p
);
GETLONG
(
sig_inception
,
p
);
GETSHORT
(
key_tag
,
p
);
if
(
type_covered
!=
type
||
!
check_date_range
(
sig_inception
,
sig_expiration
)
||
!
verifyalg_supported
(
algo
))
{
/* covers wrong type or out of date - skip */
p
=
psav
;
if
(
!
ADD_RDLEN
(
header
,
p
,
plen
,
rdlen
))
return
STAT_INSECURE
;
continue
;
}
if
(
!
extract_name
(
header
,
plen
,
&
p
,
keyname
,
1
,
0
))
return
STAT_INSECURE
;
/* 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
;
/* Sort RRset records in canonical order. */
rrset_canonical_order_ctx
.
header
=
header
;
rrset_canonical_order_ctx
.
pktlen
=
plen
;
qsort
(
rrset
,
rrsetidx
,
sizeof
(
void
*
),
rrset_canonical_order
);
alg
=
verifyalg_alloc
(
algo
);
alg
->
sig
=
p
;
alg
->
siglen
=
rdlen
-
(
p
-
psav
);
ntype
=
htons
(
type
);
nclass
=
htons
(
class
);
nsigttl
=
htonl
(
orig_ttl
);
digestalg_begin
(
alg
->
vtbl
->
digest_algo
);
digestalg_add_data
(
psav
,
18
);
wire_len
=
to_wire
(
keyname
);
digestalg_add_data
(
keyname
,
wire_len
);
from_wire
(
keyname
);
/* TODO wildcard rules : 4035 5.3.2 */
for
(
i
=
0
;
i
<
rrsetidx
;
++
i
)
{
p
=
(
unsigned
char
*
)(
rrset
[
i
]);
wire_len
=
to_wire
(
name
);
digestalg_add_data
(
name
,
wire_len
);
from_wire
(
name
);
digestalg_add_data
(
&
ntype
,
2
);
digestalg_add_data
(
&
nclass
,
2
);
digestalg_add_data
(
&
nsigttl
,
4
);
p
+=
8
;
if
(
!
digestalg_add_rdata
(
type
,
header
,
plen
,
p
))
return
STAT_INSECURE
;
}
p += 8;
if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p))
return 0;
}
int digest_len = digestalg_len();
memcpy(alg->digest, digestalg_final(), digest_len);
/* We don't need the owner name anymore; now extract the signer name */
if (!extract_name_no_compression(sigrdata+18, signer_name_rdlen, signer_name))
return 0;
memcpy
(
alg
->
digest
,
digestalg_final
(),
digestalg_len
());
out->alg = alg;
out->keytag = keytag;
out->signer_name = signer_name;
return 1;
}
if
(
key
)
{
if
(
algo_in
==
algo
&&
keytag_in
==
key_tag
&&
alg
->
vtbl
->
verify
(
alg
,
key
,
keylen
))
return
STAT_SECURE
;
}
else
{
/* iterate through all possible keys 4035 5.3.1 */
for
(;
crecp
;
crecp
=
cache_find_by_name
(
crecp
,
keyname
,
now
,
F_DNSKEY
))
if
(
crecp
->
addr
.
key
.
algo
==
algo
&&
crecp
->
addr
.
key
.
keytag
==
key_tag
&&
alg
->
vtbl
->
verify
(
alg
,
crecp
->
addr
.
key
.
keydata
,
crecp
->
uid
))
return
STAT_SECURE
;
}
}
static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey)
{
/* FIXME: keydata is non-contiguous */
return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid);
return
STAT_BOGUS
;
}
static void dnssec_parserrsig(struct dns_header *header, size_t pktlen,
unsigned char *reply, int count, char *owner,
int sigclass, int sigrdlen, unsigned char *sig)
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
int
dnssec_validate_reply
(
time_t
now
,
struct
dns_header
*
header
,
size_t
plen
,
char
*
name
,
char
*
keyname
,
int
*
class
)
{
PendingRRSIGValidation val;
/* Initiate the RRSIG validation process. The pending state is returned into val. */
if (!begin_rrsig_validation(header, pktlen, reply, count, owner, sigclass, sigrdlen, sig, &val))
return;
unsigned
char
*
ans_start
,
*
p1
,
*
p2
;
int
type1
,
class1
,
rdlen1
,
type2
,
class2
,
rdlen2
;
int
i
,
j
,
rc
;
printf("RRSIG: querying cache for DNSKEY %s (keytag: %d)\n", val.signer_name, val.keytag);
/* Look in the cache for *all* the DNSKEYs with matching signer_name and keytag */
char onekey = 0;
struct crec *crecp = NULL;
while ((crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY))) /* TODO: time(0) */
if
(
!
(
ans_start
=
skip_questions
(
header
,
plen
)))
return
STAT_INSECURE
;
for
(
p1
=
ans_start
,
i
=
0
;
i
<
ntohs
(
header
->
ancount
)
+
ntohs
(
header
->
nscount
);
i
++
)
{
onekey = 1;
if (crecp->addr.key.keytag == val.keytag
&& crecp->addr.key.algo == verifyalg_algonum(val.alg))
{
printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag);
if
(
!
extract_name
(
header
,
plen
,
&
p1
,
name
,
1
,
10
))
return
STAT_INSECURE
;
/* bad packet */
GETSHORT
(
type1
,
p1
);
GETSHORT
(
class1
,
p1
);
p1
+=
4
;
/* TTL */
GETSHORT
(
rdlen1
,
p1
);
/* Don't try and validate RRSIGs! */
if
(
type1
!=
T_RRSIG
)
{
/* Check if we've done this RRset already */
for
(
p2
=
ans_start
,
j
=
0
;
j
<
i
;
j
++
)
{
if
(
!
(
rc
=
extract_name
(
header
,
plen
,
&
p2
,
name
,
0
,
10
)))
return
STAT_INSECURE
;
/* bad packet */
GETSHORT
(
type2
,
p2
);
GETSHORT
(
class2
,
p2
);
p2
+=
4
;
/* TTL */
GETSHORT
(
rdlen2
,
p2
);
if
(
type2
==
type1
&&
class2
==
class1
&&
rc
==
1
)
break
;
/* Done it before: name, type, class all match. */
if
(
!
ADD_RDLEN
(
header
,
p2
,
plen
,
rdlen2
))
return
STAT_INSECURE
;
}
/* Not done, validate now */
if
(
j
==
i
&&
(
rc
=
validate_rrset
(
now
,
header
,
plen
,
class1
,
type1
,
name
,
keyname
,
NULL
,
0
,
0
,
0
))
!=
STAT_SECURE
)
{
*
class
=
class1
;
/* Class for DS or DNSKEY */
return
rc
;
}
}
if (end_rrsig_validation(&val, crecp))
printf("Validation OK\n");
else
printf("ERROR: Validation FAILED (%s, keytag:%d, algo:%d)\n", owner, val.keytag, verifyalg_algonum(val.alg));
}
if
(
!
ADD_RDLEN
(
header
,
p1
,
plen
,
rdlen1
))
return
STAT_INSECURE
;
}
if (!onekey)
{
printf("DNSKEY not found, need to fetch it\n");
/* TODO: store PendingRRSIGValidation in routing table,
fetch key (and make it go through dnssec_parskey), then complete validation. */
}
return
STAT_SECURE
;
}
#endif /* comment out */
/* Compute keytag (checksum to quickly index a key). See RFC4034 */
static
int
dnskey_keytag
(
int
alg
,
unsigned
char
*
rdata
,
int
rd
len
)
int
dnskey_keytag
(
int
alg
,
int
flags
,
unsigned
char
*
key
,
int
key
len
)
{
if
(
alg
==
1
)
{
/* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm.
See RFC4034, Appendix B.1 */
return
rdata
[
rdlen
-
3
]
*
256
+
rdata
[
rdlen
-
2
];
return
key
[
keylen
-
4
]
*
256
+
key
[
keylen
-
3
];
}
else
{
unsigned
long
ac
;
int
i
;
ac
=
0
;
for
(
i
=
0
;
i
<
rdlen
;
++
i
)
ac
+=
(
i
&
1
)
?
rdata
[
i
]
:
rdata
[
i
]
<<
8
;
ac
+=
(
ac
>>
16
)
&
0xFFFF
;
return
ac
&
0xFFFF
;
}
}
/* Check if the DS record (from cache) points to the DNSKEY record (from cache) */
static
int
dnskey_ds_match
(
struct
crec
*
dnskey
,
struct
crec
*
ds
)
{
if
(
dnskey
->
addr
.
key
.
keytag
!=
ds
->
addr
.
key
.
keytag
)
return
0
;
if
(
dnskey
->
addr
.
key
.
algo
!=
ds
->
addr
.
key
.
algo
)
return
0
;
unsigned
char
owner
[
MAXCDNAME
];
/* TODO: user part of daemon->namebuff */
int
owner_len
=
convert_domain_to_wire
(
cache_get_name
(
ds
),
owner
);
size_t
keylen
=
dnskey
->
uid
;
int
dig
=
ds
->
uid
;
int
digsize
;
if
(
!
digestalg_begin
(
dig
))
return
0
;
digsize
=
digestalg_len
();
digestalg_add_data
(
owner
,
owner_len
);
digestalg_add_data
(
"
\x01\x01\x03
"
,
3
);
digestalg_add_data
(
&
ds
->
addr
.
key
.
algo
,
1
);
digestalg_add_keydata
(
dnskey
->
addr
.
key
.
keydata
,
keylen
);
return
(
memcmp
(
digestalg_final
(),
ds
->
addr
.
key
.
keydata
->
key
,
digsize
)
==
0
);
}
int
dnssec_parsekey
(
struct
dns_header
*
header
,
size_t
pktlen
,
char
*
owner
,
unsigned
long
ttl
,
int
rdlen
,
unsigned
char
*
rdata
)
{
int
flags
,
proto
,
alg
;
struct
blockdata
*
key
;
struct
crec
*
crecp
;
unsigned
char
*
ordata
=
rdata
;
int
ordlen
=
rdlen
;
CHECKED_GETSHORT
(
flags
,
rdata
,
rdlen
);
CHECKED_GETCHAR
(
proto
,
rdata
,
rdlen
);
CHECKED_GETCHAR
(
alg
,
rdata
,
rdlen
);
if
(
proto
!=
3
)
return
0
;
/* Skip non-signing keys (as specified in RFC4034 */
if
(
!
(
flags
&
0x100
))
return
0
;
key
=
blockdata_alloc
((
char
*
)
rdata
,
rdlen
);
/* TODO: time(0) is correct here? */
crecp
=
cache_insert
(
owner
,
NULL
,
time
(
0
),
ttl
,
F_FORWARD
|
F_DNSKEY
);
if
(
crecp
)
{
/* TODO: improve union not to name "uid" this field */
crecp
->
uid
=
rdlen
;
crecp
->
addr
.
key
.
keydata
=
key
;
crecp
->
addr
.
key
.
algo
=
alg
;
crecp
->
addr
.
key
.
keytag
=
dnskey_keytag
(
alg
,
ordata
,
ordlen
);
printf
(
"DNSKEY: storing key for %s (keytag: %d)
\n
"
,
owner
,
crecp
->
addr
.
key
.
keytag
);
}
else
{
blockdata_free
(
key
);
/* TODO: if insertion really might fail, verify we don't depend on cache
insertion success for validation workflow correctness */
printf
(
"DNSKEY: cache insertion failure
\n
"
);
return
0
;
}
return
1
;
}
int
dnssec_parseds
(
struct
dns_header
*
header
,
size_t
pktlen
,
char
*
owner
,
unsigned
long
ttl
,
int
rdlen
,
unsigned
char
*
rdata
)
{
int
keytag
,
algo
,
dig
;
struct
blockdata
*
key
;
struct
crec
*
crec_ds
,
*
crec_key
;
CHECKED_GETSHORT
(
keytag
,
rdata
,
rdlen
);
CHECKED_GETCHAR
(
algo
,
rdata
,
rdlen
);
CHECKED_GETCHAR
(
dig
,
rdata
,
rdlen
);
if
(
!
digestalg_supported
(
dig
))
return
0
;
key
=
blockdata_alloc
((
char
*
)
rdata
,
rdlen
);
/* TODO: time(0) is correct here? */
crec_ds
=
cache_insert
(
owner
,
NULL
,
time
(
0
),
ttl
,
F_FORWARD
|
F_DS
);
if
(
!
crec_ds
)
{
blockdata_free
(
key
);
/* TODO: if insertion really might fail, verify we don't depend on cache
insertion success for validation workflow correctness */
printf
(
"DS: cache insertion failure
\n
"
);
return
0
;
ac
=
((
htons
(
flags
)
>>
8
)
|
((
htons
(
flags
)
<<
8
)
&
0xff00
))
+
0x300
+
alg
;
for
(
i
=
0
;
i
<
keylen
;
++
i
)
ac
+=
(
i
&
1
)
?
key
[
i
]
:
key
[
i
]
<<
8
;
ac
+=
(
ac
>>
16
)
&
0xffff
;
return
ac
&
0xffff
;
}
/* TODO: improve union not to name "uid" this field */
crec_ds
->
uid
=
dig
;
crec_ds
->
addr
.
key
.
keydata
=
key
;
crec_ds
->
addr
.
key
.
algo
=
algo
;
crec_ds
->
addr
.
key
.
keytag
=
keytag
;
printf
(
"DS: storing key for %s (digest: %d)
\n
"
,
owner
,
dig
);
/* Now try to find a DNSKEY which matches this DS digest. */
printf
(
"Looking for a DNSKEY matching DS %d...
\n
"
,
keytag
);
crec_key
=
NULL
;
while
((
crec_key
=
cache_find_by_name
(
crec_key
,
owner
,
time
(
0
),
F_DNSKEY
)))
/* TODO: time(0) */
{
if
(
dnskey_ds_match
(
crec_key
,
crec_ds
))
{
/* TODO: create a link within the cache: ds => dnskey */
printf
(
"MATCH FOUND for keytag %d
\n
"
,
keytag
);
return
1
;
}
}
printf
(
"ERROR: match not found for DS %d (owner: %s)
\n
"
,
keytag
,
owner
);
return
0
;
}
int
dnssec1_validate
(
struct
dns_header
*
header
,
size_t
pktlen
)
{
unsigned
char
*
p
,
*
reply
;
char
*
owner
=
daemon
->
namebuff
;
int
i
,
s
,
qtype
,
qclass
,
rdlen
;
unsigned
long
ttl
;
int
slen
[
3
]
=
{
ntohs
(
header
->
ancount
),
ntohs
(
header
->
nscount
),
ntohs
(
header
->
arcount
)
};
if
(
slen
[
0
]
+
slen
[
1
]
+
slen
[
2
]
==
0
)
return
0
;
if
(
!
(
reply
=
p
=
skip_questions
(
header
,
pktlen
)))
return
0
;
/* First, process DNSKEY/DS records and add them to the cache. */
cache_start_insert
();
for
(
i
=
0
;
i
<
slen
[
0
];
i
++
)
{
if
(
!
extract_name
(
header
,
pktlen
,
&
p
,
owner
,
1
,
10
))
return
0
;
GETSHORT
(
qtype
,
p
);
GETSHORT
(
qclass
,
p
);
GETLONG
(
ttl
,
p
);
GETSHORT
(
rdlen
,
p
);
if
(
qtype
==
T_DS
)
{
printf
(
"DS found
\n
"
);
dnssec_parseds
(
header
,
pktlen
,
owner
,
ttl
,
rdlen
,
p
);
}
else
if
(
qtype
==
T_DNSKEY
)
{
printf
(
"DNSKEY found
\n
"
);
dnssec_parsekey
(
header
,
pktlen
,
owner
,
ttl
,
rdlen
,
p
);
}
p
+=
rdlen
;
}
cache_end_insert
();
/* After we have cached DNSKEY/DS records, start looking for RRSIGs.
We want to do this in a separate step because we want the cache
to be already populated with DNSKEYs before parsing signatures. */
p
=
reply
;
for
(
s
=
0
;
s
<
3
;
++
s
)
{
reply
=
p
;
for
(
i
=
0
;
i
<
slen
[
s
];
i
++
)
{
if
(
!
extract_name
(
header
,
pktlen
,
&
p
,
owner
,
1
,
10
))
return
0
;
GETSHORT
(
qtype
,
p
);
GETSHORT
(
qclass
,
p
);
GETLONG
(
ttl
,
p
);
GETSHORT
(
rdlen
,
p
);
if
(
qtype
==
T_RRSIG
)
{
printf
(
"RRSIG found (owner: %s)
\n
"
,
owner
);
/* TODO: missing logic. We should only validate RRSIGs for which we
have a valid DNSKEY that is referenced by a DS record upstream.
There is a memory vs CPU conflict here; should we validate everything
to save memory and thus waste CPU, or better first acquire all information
(wasting memory) and then doing the minimum CPU computations required? */
dnssec_parserrsig
(
header
,
pktlen
,
reply
,
slen
[
s
],
owner
,
qclass
,
rdlen
,
p
);
}
p
+=
rdlen
;
}
}
return
1
;
}
#endif
/* HAVE_DNSSEC */
src/forward.c
View file @
0fc2f313
...
...
@@ -344,7 +344,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
#ifdef HAVE_DNSSEC
if
(
option_bool
(
OPT_DNSSEC_VALID
))
plen
=
add_do_bit
(
header
,
plen
,
((
char
*
)
header
)
+
PACKETSZ
);
{
plen
=
add_do_bit
(
header
,
plen
,
((
char
*
)
header
)
+
PACKETSZ
);
header
->
hb4
|=
HB4_CD
;
}
#endif
while
(
1
)
...
...
@@ -550,7 +553,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
SET_RCODE
(
header
,
NOERROR
);
}
if
(
extract_addresses
(
header
,
n
,
daemon
->
namebuff
,
now
,
sets
,
is_sign
,
check_rebind
,
no_cache
))
if
(
extract_addresses
(
header
,
n
,
daemon
->
namebuff
,
now
,
sets
,
is_sign
,
check_rebind
,
no_cache
,
cache_secure
))
{
my_syslog
(
LOG_WARNING
,
_
(
"possible DNS-rebind attack detected: %s"
),
daemon
->
namebuff
);
munged
=
1
;
...
...
@@ -678,41 +681,51 @@ void reply_query(int fd, int family, time_t now)
if
(
option_bool
(
OPT_DNSSEC_VALID
)
&&
!
(
forward
->
flags
&
FREC_CHECKING_DISABLED
))
{
int
status
;
int
class
;
/* We've had a reply already, which we're validating. Ignore this duplicate */
if
(
forward
->
stash
)
return
;
if
(
forward
->
flags
&
FREC_DNSKEY_QUERY
)
status
=
dnssec_validate_by_ds
(
now
,
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
forward
->
class
);
status
=
dnssec_validate_by_ds
(
now
,
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
forward
->
class
);
else
if
(
forward
->
flags
&
FREC_DS_QUERY
)
status
=
dnssec_validate_ds
(
now
,
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
forward
->
class
);
else
status
=
dnssec_validate_reply
(
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
&
forward
->
class
);
status
=
dnssec_validate_reply
(
now
,
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
&
forward
->
class
);
/* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */
if
(
status
==
STAT_NEED_DS
||
status
==
STAT_NEED_KEY
)
{
struct
frec
*
new
;
if
((
forward
->
stash
=
blockdata_alloc
((
char
*
)
header
,
n
)))
if
((
new
=
get_new_frec
(
now
,
NULL
,
1
)))
{
forward
->
stash_len
=
n
;
struct
frec
*
next
=
new
->
next
;
*
new
=
*
forward
;
/* copy everything, then overwrite */
new
->
next
=
next
;
new
->
stash
=
NULL
;
new
->
blocking_query
=
NULL
;
new
->
flags
&=
~
(
FREC_DNSKEY_QUERY
|
FREC_DS_QUERY
);
if
((
new
=
get_new_frec
(
now
,
NULL
,
1
)))
if
((
forward
->
stash
=
blockdata_alloc
((
char
*
)
header
,
n
)))
{
int
fd
;
new
=
forward
;
/* copy everything, then overwrite */
forward
->
stash_len
=
n
;
new
->
dependent
=
forward
;
/* to find query awaiting new one. */
forward
->
blocking_query
=
new
;
/* for garbage cleaning */
/* validate routines leave name of required record in daemon->
namebuff
*/
/* validate routines leave name of required record in daemon->
keyname
*/
if
(
status
==
STAT_NEED_KEY
)
{
new
->
flags
|=
FREC_DNSKEY_QUERY
;
nn
=
dnssec_generate_query
(
header
,
daemon
->
namebuff
,
class
,
T_DNSKEY
);
nn
=
dnssec_generate_query
(
header
,
daemon
->
keyname
,
forward
->
class
,
T_DNSKEY
,
&
server
->
addr
);
}
else
if
(
status
==
STAT_NEED_DS
)
{
new
->
flags
|=
FREC_DS_QUERY
;
nn
=
dnssec_generate_query
(
header
,
daemon
->
namebuff
,
class
,
T_DS
);
nn
=
dnssec_generate_query
(
header
,
daemon
->
keyname
,
forward
->
class
,
T_DS
,
&
server
->
addr
);
}
new
->
crc
=
questions_crc
(
header
,
nn
,
daemon
->
namebuff
);
new
->
new_id
=
get_id
(
new
->
crc
);
...
...
@@ -739,9 +752,11 @@ void reply_query(int fd, int family, time_t now)
}
/* Send DNSSEC query to same server as original query */
while
(
sendto
(
fd
,
(
char
*
)
header
,
nn
,
0
,
&
server
->
addr
.
sa
,
sa_len
(
&
server
->
addr
))
==
-
1
&&
retry_send
());
while
(
sendto
(
fd
,
(
char
*
)
header
,
nn
,
0
,
&
server
->
addr
.
sa
,
sa_len
(
&
server
->
addr
))
==
-
1
&&
retry_send
());
server
->
queries
++
;
}
}
return
;
}
...
...
@@ -750,35 +765,56 @@ void reply_query(int fd, int family, time_t now)
and validate them with the new data. Failure to find needed data here is an internal error.
Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates,
return it to the original requestor. */
while
(
forward
->
dependent
)
if
(
forward
->
flags
&
(
FREC_DNSKEY_QUERY
|
FREC_DS_QUERY
)
)
{
struct
frec
*
prev
=
forward
->
dependent
;
free_frec
(
forward
);
forward
=
prev
;
blockdata_retrieve_and_free
(
forward
->
stash
,
forward
->
stash_len
,
(
void
*
)
header
);
n
=
forward
->
stash_len
;
if
(
status
==
STAT_SECURE
)
while
(
forward
->
dependent
)
{
if
(
forward
->
flags
&
FREC_DNSKEY_QUERY
)
status
=
dnssec_validate_by_ds
(
now
,
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
forward
->
class
);
else
if
(
forward
->
flags
&
FREC_DS_QUERY
)
status
=
dnssec_validate_dnskey
(
now
,
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
forward
->
class
);
struct
frec
*
prev
;
if
(
status
==
STAT_NEED_DS
||
status
==
STAT_NEED_KEY
)
my_syslog
(
LOG_ERR
,
_
(
"Unexpected missing data for DNSSEC validation"
));
if
(
status
==
STAT_SECURE
)
{
if
(
forward
->
flags
&
FREC_DNSKEY_QUERY
)
status
=
dnssec_validate_by_ds
(
now
,
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
forward
->
class
);
else
if
(
forward
->
flags
&
FREC_DS_QUERY
)
status
=
dnssec_validate_ds
(
now
,
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
forward
->
class
);
}
prev
=
forward
->
dependent
;
free_frec
(
forward
);
forward
=
prev
;
forward
->
blocking_query
=
NULL
;
/* already gone */
blockdata_retrieve
(
forward
->
stash
,
forward
->
stash_len
,
(
void
*
)
header
);
n
=
forward
->
stash_len
;
}
/* All DNSKEY and DS records done and in cache, now finally validate original
answer, provided last DNSKEY is OK. */
if
(
status
==
STAT_SECURE
)
status
=
dnssec_validate_reply
(
now
,
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
&
forward
->
class
);
if
(
status
==
STAT_NEED_DS
||
status
==
STAT_NEED_KEY
)
{
my_syslog
(
LOG_ERR
,
_
(
"Unexpected missing data for DNSSEC validation"
));
status
=
STAT_INSECURE
;
}
}
/* All DNSKEY and DS records done and in cache, now finally validate original
answer, provided last DNSKEY is OK. */
if
(
status
==
STAT_SECURE
)
status
=
dnssec_validate_reply
(
header
,
n
,
daemon
->
namebuff
,
daemon
->
keyname
,
&
forward
->
class
)
;
log_query
(
F_KEYTAG
|
F_SECSTAT
,
"result"
,
NULL
,
status
==
STAT_SECURE
?
"SECURE"
:
(
status
==
STAT_INSECURE
?
"INSECURE"
:
"BOGUS"
));
no_cache_dnssec
=
0
;
if
(
status
==
STAT_SECURE
)
cache_secure
=
1
;
/* TODO return SERVFAIL here */
else
if
(
status
==
STAT_BOGUS
)
no_cache_dnssec
=
1
;
/* restore CD bit to the value in the query */
if
(
forward
->
flags
&
FREC_CHECKING_DISABLED
)
header
->
hb4
|=
HB4_CD
;
else
header
->
hb4
&=
~
HB4_CD
;
}
#endif
...
...
@@ -1342,7 +1378,6 @@ static struct randfd *allocate_rfd(int family)
return
NULL
;
/* doom */
}
static
void
free_frec
(
struct
frec
*
f
)
{
if
(
f
->
rfd4
&&
--
(
f
->
rfd4
->
refcount
)
==
0
)
...
...
@@ -1361,7 +1396,10 @@ static void free_frec(struct frec *f)
#ifdef HAVE_DNSSEC
if
(
f
->
stash
)
blockdata_free
(
f
->
stash
);
{
blockdata_free
(
f
->
stash
);
f
->
stash
=
NULL
;
}
/* Anything we're waiting on is pointless now, too */
if
(
f
->
blocking_query
)
...
...
src/option.c
View file @
0fc2f313
...
...
@@ -139,6 +139,7 @@ struct myoption {
#define LOPT_QUIET_DHCP6 327
#define LOPT_QUIET_RA 328
#define LOPT_SEC_VALID 329
#define LOPT_DNSKEY 330
#ifdef HAVE_GETOPT_LONG
...
...
@@ -276,6 +277,7 @@ static const struct myoption opts[] =
{
"ipset"
,
1
,
0
,
LOPT_IPSET
},
{
"synth-domain"
,
1
,
0
,
LOPT_SYNTH
},
{
"dnssec"
,
0
,
0
,
LOPT_SEC_VALID
},
{
"dnskey"
,
1
,
0
,
LOPT_DNSKEY
},
#ifdef OPTION6_PREFIX_CLASS
{
"dhcp-prefix-class"
,
1
,
0
,
LOPT_PREF_CLSS
},
#endif
...
...
@@ -428,6 +430,7 @@ static struct {
{
LOPT_SYNTH
,
ARG_DUP
,
"<domain>,<range>,[<prefix>]"
,
gettext_noop
(
"Specify a domain and address range for synthesised names"
),
NULL
},
#ifdef HAVE_DNSSEC
{
LOPT_SEC_VALID
,
OPT_DNSSEC_VALID
,
NULL
,
gettext_noop
(
"Activate DNSSEC validation"
),
NULL
},
{
LOPT_DNSKEY
,
ARG_DUP
,
"<domain>,<algo>,<key>"
,
gettext_noop
(
"Specify trust anchor DNSKEY"
),
NULL
},
#endif
#ifdef OPTION6_PREFIX_CLASS
{
LOPT_PREF_CLSS
,
ARG_DUP
,
"set:tag,<class>"
,
gettext_noop
(
"Specify DHCPv6 prefix class"
),
NULL
},
...
...
@@ -3670,9 +3673,34 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
daemon
->
host_records_tail
=
new
;
break
;
}
#ifdef HAVE_DNSSEC
case
LOPT_DNSKEY
:
{
struct
dnskey
*
new
=
opt_malloc
(
sizeof
(
struct
dnskey
));
char
*
key64
,
*
algo
;
if
(
!
(
comma
=
split
(
arg
))
||
!
(
algo
=
split
(
comma
))
||
!
(
key64
=
split
(
algo
))
||
!
atoi_check16
(
comma
,
&
new
->
flags
)
||
!
atoi_check16
(
algo
,
&
new
->
algo
)
||
!
(
new
->
name
=
canonicalise_opt
(
arg
)))
ret_err
(
_
(
"bad DNSKEY"
));
/* Upper bound on length */
new
->
key
=
opt_malloc
((
3
*
strlen
(
key64
)
/
4
));
unhide_metas
(
key64
);
if
((
new
->
keylen
=
parse_base64
(
key64
,
new
->
key
))
==
-
1
)
ret_err
(
_
(
"bad base64 in DNSKEY"
));
new
->
next
=
daemon
->
dnskeys
;
daemon
->
dnskeys
=
new
;
break
;
}
#endif
default:
ret_err
(
_
(
"unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DBus support)"
));
ret_err
(
_
(
"unsupported option (check that dnsmasq was compiled with DHCP/TFTP/D
NSSEC/D
Bus support)"
));
}
...
...
src/rfc1035.c
View file @
0fc2f313
...
...
@@ -577,10 +577,13 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
return
plen
;
/* Too big */
}
PUTSHORT
(
optno
,
p
);
PUTSHORT
(
optlen
,
p
);
memcpy
(
p
,
opt
,
optlen
);
p
+=
optlen
;
if
(
optno
!=
0
)
{
PUTSHORT
(
optno
,
p
);
PUTSHORT
(
optlen
,
p
);
memcpy
(
p
,
opt
,
optlen
);
p
+=
optlen
;
}
PUTSHORT
(
p
-
datap
,
lenp
);
return
p
-
(
unsigned
char
*
)
header
;
...
...
@@ -889,7 +892,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name)
expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */
int
extract_addresses
(
struct
dns_header
*
header
,
size_t
qlen
,
char
*
name
,
time_t
now
,
char
**
ipsets
,
int
is_sign
,
int
check_rebind
,
int
no_cache_dnssec
)
char
**
ipsets
,
int
is_sign
,
int
check_rebind
,
int
no_cache_dnssec
,
int
secure
)
{
unsigned
char
*
p
,
*
p1
,
*
endrr
,
*
namep
;
int
i
,
j
,
qtype
,
qclass
,
aqtype
,
aqclass
,
ardlen
,
res
,
searched_soa
=
0
;
...
...
@@ -919,6 +922,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
struct
crec
*
cpp
=
NULL
;
int
flags
=
RCODE
(
header
)
==
NXDOMAIN
?
F_NXDOMAIN
:
0
;
unsigned
long
cttl
=
ULONG_MAX
,
attl
;
if
(
RCODE
(
header
)
==
NXDOMAIN
)
flags
|=
F_NXDOMAIN
;
if
(
secure
)
flags
|=
F_DNSSECOK
;
namep
=
p
;
if
(
!
extract_name
(
header
,
qlen
,
&
p
,
name
,
1
,
4
))
...
...
@@ -1446,7 +1455,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
int
dryrun
=
0
,
sec_reqd
=
0
;
int
is_sign
;
struct
crec
*
crecp
;
int
nxdomain
=
0
,
auth
=
1
,
trunc
=
0
;
int
nxdomain
=
0
,
auth
=
1
,
trunc
=
0
,
sec_data
=
1
;
struct
mx_srv_record
*
rec
;
/* If there is an RFC2671 pseudoheader then it will be overwritten by
...
...
@@ -1621,6 +1630,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if
(
qtype
==
T_ANY
&&
!
(
crecp
->
flags
&
(
F_HOSTS
|
F_DHCP
)))
continue
;
if
(
!
(
crecp
->
flags
&
F_DNSSECOK
))
sec_data
=
0
;
if
(
crecp
->
flags
&
F_NEG
)
{
ans
=
1
;
...
...
@@ -1794,6 +1806,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if
(
qtype
==
T_ANY
&&
!
(
crecp
->
flags
&
(
F_HOSTS
|
F_DHCP
|
F_CONFIG
)))
break
;
if
(
!
(
crecp
->
flags
&
F_DNSSECOK
))
sec_data
=
0
;
if
(
crecp
->
flags
&
F_CNAME
)
{
char
*
cname_target
=
cache_get_cname_target
(
crecp
);
...
...
@@ -1868,6 +1883,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if
((
crecp
=
cache_find_by_name
(
NULL
,
name
,
now
,
F_CNAME
))
&&
(
qtype
==
T_CNAME
||
(
crecp
->
flags
&
(
F_HOSTS
|
F_DHCP
|
F_CONFIG
))))
{
if
(
!
(
crecp
->
flags
&
F_DNSSECOK
))
sec_data
=
0
;
ans
=
1
;
if
(
!
dryrun
)
{
...
...
@@ -2046,7 +2064,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* truncation */
if
(
trunc
)
header
->
hb3
|=
HB3_TC
;
header
->
hb4
&=
~
HB4_AD
;
if
(
option_bool
(
OPT_DNSSEC_VALID
)
||
option_bool
(
OPT_DNSSEC_PROXY
))
if
(
sec_data
)
header
->
hb4
|=
HB4_AD
;
if
(
nxdomain
)
SET_RCODE
(
header
,
NXDOMAIN
);
else
...
...
src/util.c
View file @
0fc2f313
...
...
@@ -109,10 +109,10 @@ static int check_name(char *in)
if
(
in
[
l
-
1
]
==
'.'
)
{
if
(
l
==
1
)
return
0
;
in
[
l
-
1
]
=
0
;
nowhite
=
1
;
}
for
(;
(
c
=
*
in
);
in
++
)
{
if
(
c
==
'.'
)
...
...
@@ -482,6 +482,66 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
return
i
;
}
#ifdef HAVE_DNSSEC
static
int
charval
(
char
c
)
{
if
(
c
>=
'A'
&&
c
<=
'Z'
)
return
c
-
'A'
;
if
(
c
>=
'a'
&&
c
<=
'z'
)
return
c
-
'a'
+
26
;
if
(
c
>=
'0'
&&
c
<=
'9'
)
return
c
-
'0'
+
52
;
if
(
c
==
'+'
)
return
62
;
if
(
c
==
'/'
)
return
63
;
if
(
c
==
'='
)
return
-
1
;
return
-
2
;
}
int
parse_base64
(
char
*
in
,
char
*
out
)
{
char
*
p
=
out
;
int
i
,
val
[
4
];
while
(
*
in
)
{
for
(
i
=
0
;
i
<
4
;
i
++
)
{
while
(
*
in
==
' '
)
in
++
;
if
(
*
in
==
0
)
return
-
1
;
if
((
val
[
i
]
=
charval
(
*
in
++
))
==
-
2
)
return
-
1
;
}
while
(
*
in
==
' '
)
in
++
;
if
(
val
[
1
]
==
-
1
)
return
-
1
;
/* too much padding */
*
p
++
=
(
val
[
0
]
<<
2
)
|
(
val
[
1
]
>>
4
);
if
(
val
[
2
]
!=
-
1
)
*
p
++
=
(
val
[
1
]
<<
4
)
|
(
val
[
2
]
>>
2
);
if
(
val
[
3
]
!=
-
1
)
*
p
++
=
(
val
[
2
]
<<
6
)
|
val
[
3
];
}
return
p
-
out
;
}
#endif
/* return 0 for no match, or (no matched octets) + 1 */
int
memcmp_masked
(
unsigned
char
*
a
,
unsigned
char
*
b
,
int
len
,
unsigned
int
mask
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment