Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
qdm12 committed Nov 30, 2023
1 parent 09e170e commit c779e27
Show file tree
Hide file tree
Showing 15 changed files with 1,004 additions and 0 deletions.
201 changes: 201 additions & 0 deletions pkg/dnssec/chain.go
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
}
68 changes: 68 additions & 0 deletions pkg/dnssec/client.go
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
}
Loading

0 comments on commit c779e27

Please sign in to comment.