-
-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
1,004 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
package dnssec | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/miekg/dns" | ||
"github.com/qdm12/dns/v2/internal/server" | ||
) | ||
|
||
// delegationChain is the DNSSEC chain of trust from the | ||
// queried zone to the root (.) zone. | ||
// The first signed zone is the queried zone, and the last | ||
// signed zone is the root zone. | ||
// See https://www.ietf.org/rfc/rfc4033.txt | ||
type delegationChain []*signedZone | ||
|
||
// buildDelegationChain queries the RRs required for the zone validation. | ||
// It begins the queries at the desired zone and then go | ||
// up the delegation tree until it reaches the root zone. | ||
// It returns a new delegation chain of signed zones where the | ||
// first signed zone (index 0) is the child zone and the last signed | ||
// zone is the root zone. | ||
func buildDelegationChain(ctx context.Context, exchange server.Exchange, | ||
zone string, qClass uint16) (chain delegationChain, err error) { | ||
zoneParts := strings.Split(zone, ".") | ||
|
||
type result struct { | ||
i int | ||
signedZone *signedZone | ||
err error | ||
} | ||
results := make(chan result) | ||
|
||
for i := range zoneParts { | ||
go func(i int, results chan<- result) { | ||
// the following zone names are queried: | ||
// 'example.com.', 'com.', '.' | ||
zoneName := dns.Fqdn(strings.Join(zoneParts[i:], ".")) | ||
signedZone, err := queryDelegation(ctx, exchange, zoneName, qClass) | ||
if err != nil { | ||
err = fmt.Errorf("querying delegation for zone %s: %w", | ||
zone, err) | ||
} | ||
results <- result{i: i, signedZone: signedZone, err: err} | ||
}(i, results) | ||
} | ||
|
||
chain = make(delegationChain, len(zoneParts)) | ||
for range zoneParts { | ||
result := <-results | ||
if result.err != nil { | ||
if err == nil { | ||
err = result.err | ||
} | ||
continue | ||
} | ||
chain[result.i] = result.signedZone | ||
} | ||
close(results) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return chain, nil | ||
} | ||
|
||
// queryDelegation obtains the DNSKEY records and the DS | ||
// records for a given zone, and creates a signed zone with | ||
// this information. It does not query the (non existent) | ||
// DS record for the root zone. | ||
func queryDelegation(ctx context.Context, exchange server.Exchange, | ||
zone string, qClass uint16) (sz *signedZone, err error) { | ||
ctx, cancel := context.WithCancel(ctx) | ||
defer cancel() | ||
|
||
type result struct { | ||
qType uint16 // TODO use rrsig type covered instead | ||
signedRRSet signedRRSet | ||
err error | ||
} | ||
results := make(chan result) | ||
|
||
go func() { | ||
signedRRSet, err := queryDNSKey(ctx, exchange, zone, qClass) | ||
results <- result{qType: dns.TypeDNSKEY, signedRRSet: signedRRSet, err: err} | ||
}() | ||
|
||
go func() { | ||
// Note for the root zone ".", the DS record is not queried since | ||
// it does not exist. | ||
signedRRSet, err := queryDS(ctx, exchange, zone, qClass) | ||
results <- result{qType: dns.TypeDS, signedRRSet: signedRRSet, err: err} | ||
}() | ||
|
||
sz = &signedZone{ | ||
zone: zone, | ||
} | ||
|
||
const parallelQueries = 2 | ||
for i := 0; i < parallelQueries; i++ { | ||
result := <-results | ||
if result.err != nil { | ||
if err == nil { // first error encountered | ||
err = result.err | ||
cancel() | ||
} | ||
continue | ||
} | ||
if result.qType == dns.TypeDS { | ||
// For the root zone ".", both dsRRSig and dsRRSet are nil. | ||
sz.dsRRSet = result.signedRRSet | ||
} else { | ||
sz.dnsKeyRRSet = result.signedRRSet | ||
sz.keyTagToDNSKey = dnsKeyRRSetToMap(result.signedRRSet.rrset) | ||
} | ||
} | ||
close(results) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return sz, nil | ||
} | ||
|
||
var ( | ||
ErrSignedRecordNotFound = errors.New("signed record not found") | ||
ErrNotSignedRRSetReceived = errors.New("not signed RRSet received") | ||
) | ||
|
||
func queryDNSKey(ctx context.Context, exchange server.Exchange, | ||
zone string, qClass uint16) (signedrrset signedRRSet, err error) { | ||
notSignedRRSet, signedrrset, err := fetchSingleRRSigRRSet(ctx, | ||
exchange, zone, qClass, dns.TypeDNSKEY) | ||
switch { | ||
case err != nil: | ||
return signedRRSet{}, err | ||
case len(notSignedRRSet) > 0: | ||
for _, rr := range signedrrset.rrset { | ||
fmt.Println("\n\n===> Received signed RR:", rr) | ||
} | ||
for _, rr := range notSignedRRSet { | ||
fmt.Println("\n\n===> Received not signed RR:", rr) | ||
} | ||
|
||
return signedRRSet{}, fmt.Errorf("for %s: %w", | ||
queryParamsToString(zone, qClass, dns.TypeDNSKEY), | ||
ErrNotSignedRRSetReceived) | ||
case len(signedrrset.rrset) == 0: | ||
return signedRRSet{}, fmt.Errorf("for %s: %w", | ||
queryParamsToString(zone, qClass, dns.TypeDNSKEY), | ||
ErrSignedRecordNotFound) | ||
} | ||
return signedrrset, nil | ||
} | ||
|
||
func queryDS(ctx context.Context, exchange server.Exchange, | ||
zone string, qClass uint16) (signedrrset signedRRSet, | ||
err error) { | ||
if zone == "." { | ||
// The root zone has no RRSIG + DS record. | ||
// The root zone DS record is the root anchor, | ||
// and it is not signed by an RRSIG. | ||
return signedRRSet{}, nil | ||
} | ||
notSignedRRSet, signedrrset, err := fetchSingleRRSigRRSet(ctx, | ||
exchange, zone, qClass, dns.TypeDS) | ||
switch { | ||
case err != nil: | ||
return signedRRSet{}, err | ||
case len(notSignedRRSet) > 0: | ||
return signedRRSet{}, fmt.Errorf("for %s: %w", | ||
queryParamsToString(zone, qClass, dns.TypeDNSKEY), | ||
ErrNotSignedRRSetReceived) | ||
case len(signedrrset.rrset) == 0: | ||
return signedRRSet{}, fmt.Errorf("for %s: %w", | ||
queryParamsToString(zone, qClass, dns.TypeDNSKEY), | ||
ErrSignedRecordNotFound) | ||
} | ||
return signedrrset, nil | ||
} | ||
|
||
func dnsKeyRRSetToMap(rrset []dns.RR) (keyTagToDNSKey map[uint16]*dns.DNSKEY) { | ||
keyTagToDNSKey = make(map[uint16]*dns.DNSKEY, len(rrset)) | ||
for _, rr := range rrset { | ||
if rr.Header().Rrtype != dns.TypeDNSKEY { | ||
continue | ||
} | ||
dnsKey, ok := rr.(*dns.DNSKEY) | ||
if !ok { | ||
panic(fmt.Sprintf("RR is of type %T and not of type *dns.DNSKEY", rr)) | ||
} | ||
|
||
keyTagToDNSKey[dnsKey.KeyTag()] = dnsKey | ||
} | ||
return keyTagToDNSKey | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package dnssec | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/miekg/dns" | ||
"github.com/qdm12/dns/v2/internal/server" | ||
) | ||
|
||
type Client struct { | ||
exchange server.Exchange | ||
} | ||
|
||
func New(exchange server.Exchange) *Client { | ||
return &Client{ | ||
exchange: exchange, | ||
} | ||
} | ||
|
||
func (c *Client) Exchange(ctx context.Context, request *dns.Msg) ( | ||
response *dns.Msg, err error) { | ||
response = new(dns.Msg) | ||
|
||
for _, question := range request.Question { | ||
rrset, err := fetchAndValidateZone(ctx, c.exchange, | ||
question.Name, question.Qclass, question.Qtype) | ||
if err != nil { | ||
return nil, fmt.Errorf("validating %s %s %s: %w", | ||
question.Name, dns.ClassToString[question.Qclass], | ||
dns.TypeToString[question.Qtype], err) | ||
} | ||
|
||
response.Answer = append(response.Answer, rrset...) | ||
} | ||
|
||
return response, nil | ||
} | ||
|
||
func fetchAndValidateZone(ctx context.Context, exchange server.Exchange, | ||
zone string, qClass, qType uint16) (rrset []dns.RR, err error) { | ||
signedRRSets, notSignedRRSet, err := fetchSignedRRSets(ctx, exchange, zone, qClass, qType) | ||
if err != nil { | ||
return nil, fmt.Errorf("fetching desired zone RRSet with RRSig: %w", err) | ||
} | ||
for _, rr := range notSignedRRSet { | ||
fmt.Println("\n\n===> Received not signed RR:", rr) | ||
} | ||
|
||
delegationChain, err := buildDelegationChain(ctx, exchange, zone, qClass) | ||
if err != nil { | ||
return nil, fmt.Errorf("building delegation chain: %w", err) | ||
} | ||
|
||
minRRSetSize := len(signedRRSets) // 1 RR per RRSig | ||
rrset = make([]dns.RR, 0, minRRSetSize) | ||
for _, signedRRSet := range signedRRSets { | ||
err = verifyWithChain(signedRRSet, delegationChain) | ||
if err != nil { | ||
return nil, fmt.Errorf("verifying RRSet of type %s with RRSig "+ | ||
"and delegation chain: %w", | ||
signedRRSet.Type(), err) | ||
} | ||
rrset = append(rrset, signedRRSet.rrset...) | ||
} | ||
|
||
return rrset, nil | ||
} |
Oops, something went wrong.