Fix client side TSIG

Redesign of TSIG. Validation is on the TOOD - this can be
done in the same way as in the server.
This commit is contained in:
Miek Gieben 2012-03-01 22:40:34 +01:00
parent 172c89675c
commit 3232814d1b
9 changed files with 104 additions and 67 deletions

View File

@ -6,15 +6,13 @@ need to be fixed.
* Speed, we can always go faster. A simple reflect server now hits 35/45K qps
* go test; only works correct on my machine
* Add handy zone data structure (r/b tree)? Or not...
* Use the Exchange structure to deal with errors when resolving, esp. Timeout
* Add tsig check in 'q'?
* Tsig is handled in the library, api for querying tsig status
* Query source address?
* NSECx bitmap length
array of 256 block lens set to 0. scan RRs, save highest RR / 8 in
each block. len is 2 * # non-0 blocks + sum block len
We now allocate 32 bytes for each nsec3 seen
## Examples to add
* Nameserver, with a small zone, 1 KSK and online signing;

View File

@ -39,7 +39,7 @@ type reply struct {
conn net.Conn
tsigRequestMAC string
tsigTimersOnly bool
tsigStatus int
tsigStatus int
}
// A Request is a incoming message from a Client.
@ -150,6 +150,7 @@ func NewClient() *Client {
c.QueryChan = DefaultQueryChan
c.ReadTimeout = 2 * 1e9
c.WriteTimeout = 2 * 1e9
c.TsigSecret = make(map[string]string)
return c
}
@ -382,25 +383,18 @@ func (w *reply) readClient(p []byte) (n int, err error) {
// signature is calculated.
func (w *reply) Send(m *Msg) error {
if m.IsTsig() {
secret := m.Extra[len(m.Extra)-1].(*RR_TSIG).Hdr.Name
_, ok := w.Client().TsigSecret[secret]
if !ok {
name := m.Extra[len(m.Extra)-1].(*RR_TSIG).Hdr.Name
if _, ok := w.Client().TsigSecret[name]; !ok {
return ErrSecret
}
// TODO(mg): compression makes this fail
if err := TsigGenerate(m, w.Client().TsigSecret[secret], w.tsigRequestMAC, w.tsigTimersOnly); err != nil {
out, mac, err := TsigGenerate(m, w.Client().TsigSecret[name], w.tsigRequestMAC, w.tsigTimersOnly)
if err != nil {
return err
}
w.tsigRequestMAC = mac
if _, err = w.writeClient(out); err != nil {
return err
}
w.tsigRequestMAC = m.Extra[len(m.Extra)-1].(*RR_TSIG).MAC // Save the requestMAC for the next packet
}
out, ok := m.Pack()
if !ok {
return ErrPack
}
// Tsig calculation should happen here
_, err := w.writeClient(out)
if err != nil {
return err
}
return nil
}

View File

@ -7,6 +7,7 @@ import (
"os"
"strconv"
"strings"
"time"
)
var dnskey *dns.RR_DNSKEY
@ -32,7 +33,7 @@ func main() {
short := flag.Bool("short", false, "abbreviate long DNSSEC records")
check := flag.Bool("check", false, "check internal DNSSEC consistency")
anchor := flag.String("anchor", "", "use the DNSKEY in this file for interal DNSSEC consistency")
//tsig := flag.String("tsig", "", "request tsig with key: [hmac:]name:key")
tsig := flag.String("tsig", "", "request tsig with key: [hmac:]name:key")
port := flag.Int("port", 53, "port number to use")
aa := flag.Bool("aa", false, "set AA flag in query")
ad := flag.Bool("ad", false, "set AD flag in query")
@ -127,9 +128,7 @@ Flags:
nameserver = string([]byte(nameserver)[1:]) // chop off @
nameserver += ":" + strconv.Itoa(*port)
// ipv6 todo
// We use the async query handling, just to show how
// it is to be used.
// We use the async query handling, just to show how it is to be used.
dns.HandleQueryFunc(".", q)
dns.ListenAndQuery(nil, nil)
c := dns.NewClient()
@ -163,6 +162,16 @@ Flags:
if *query {
fmt.Printf("%s\n", m.String())
}
// Add tsig
if *tsig != "" {
if algo, name, secret, ok := tsigKeyParse(*tsig); ok {
m.SetTsig(name, algo, 300, uint64(time.Now().Unix()))
c.TsigSecret[name] = secret;
} else {
fmt.Fprintf(os.Stderr, "TSIG key error\n")
return
}
}
c.Do(m, nameserver)
}
@ -219,6 +228,24 @@ forever:
}
}
func tsigKeyParse(s string) (algo, name, secret string, ok bool) {
s1 := strings.SplitN(s, ":", 3)
switch len(s1) {
case 2:
return "hmac-md5.sig-alg.reg.int.", s1[0], s1[1], true
case 3:
switch s1[0] {
case "hmac-md5":
return "hmac-md5.sig-alg.reg.int.", s1[0], s1[1], true
case "hmac-sha1":
return "hmac-sha1.", s1[1], s1[2], true
case "hmac-sha256":
return "hmac-sha256.", s1[1], s1[2], true
}
}
return
}
func sectionCheck(set []dns.RR, server string, tcp bool) {
var key *dns.RR_DNSKEY
for _, rr := range set {

2
msg.go
View File

@ -42,7 +42,7 @@ var (
ErrTime error = &Error{Err: "dns: bad time"}
ErrNoSig error = &Error{Err: "dns: no signature found"}
ErrSig error = &Error{Err: "dns: bad signature"}
ErrSecret error = &Error{Err: "dns: no secret defined"}
ErrSecret error = &Error{Err: "dns: no secrets defined"}
ErrSigGen error = &Error{Err: "dns: bad signature generation"}
ErrAuth error = &Error{Err: "dns: bad authentication"}
ErrXfrSoa error = &Error{Err: "dns: no SOA seen"}

View File

@ -189,10 +189,10 @@ func TestParseDirectiveMisc(t *testing.T) {
// Another one hear, geared to NSECx
func TestParseNSEC(t *testing.T) {
nsectests := map[string]string{
"nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F",
"nl. IN NSEC3PARAM 1 0 5 30923C44C6CBBB8F": "nl.\t3600\tIN\tNSEC3PARAM\t1 0 5 30923C44C6CBBB8F",
"p2209hipbpnm681knjnu0m1febshlv4e.nl. IN NSEC3 1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM": "p2209hipbpnm681knjnu0m1febshlv4e.nl.\t3600\tIN\tNSEC3\t1 1 5 30923C44C6CBBB8F P90DG1KE8QEAN0B01613LHQDG0SOJ0TA NS SOA TXT RRSIG DNSKEY NSEC3PARAM",
"localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC",
"localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC TYPE65534": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC TYPE65534",
"localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC",
"localhost.dnssex.nl. IN NSEC www.dnssex.nl. A RRSIG NSEC TYPE65534": "localhost.dnssex.nl.\t3600\tIN\tNSEC\twww.dnssex.nl. A RRSIG NSEC TYPE65534",
}
for i, o := range nsectests {
rr, e := NewRR(i)

View File

@ -4,24 +4,39 @@
package dns
// RawSetId sets the message ID in buf. The offset 'off' must
// be positioned at the beginning of the message.
func RawSetId(msg []byte, off int, id uint16) bool {
msg[off], msg[off+1] = packUint16(id)
return true
// RawSetId sets the message ID in buf.
func RawSetId(msg []byte, i uint16) {
msg[0], msg[1] = packUint16(i)
}
// RawSetQuestionLen sets the len of the question section.
func RawSetQuestionLen(msg []byte, i uint16) {
msg[4], msg[5] = packUint16(i)
}
// RawSetAnswerLen sets the len of the question section.
func RawSetAnswerLen(msg []byte, i uint16) {
msg[6], msg[7] = packUint16(i)
}
// RawSetsNsLen sets the len of the question section.
func RawSetNsLen(msg []byte, i uint16) {
msg[8], msg[9] = packUint16(i)
}
// RawSetExtraLen sets the len of the question section.
func RawSetExtraLen(msg []byte, i uint16) {
msg[10], msg[11] = packUint16(i)
}
// RawSetRdlength sets the rdlength in the header of
// the RR. The offset 'off' must be positioned at the
// start of the header of the RR, 'end' must be the
// end of the RR.
func RawSetRdlength(msg []byte, off, end int) bool {
// end of the RR. There is no check if we overrun the buffer.
func RawSetRdlength(msg []byte, off, end int) {
// We are at the start of the header, walk the domainname (might be compressed)
Loop:
for {
if off > len(msg) {
return false
}
c := int(msg[off])
off++
switch c & 0xC0 {
@ -40,11 +55,8 @@ Loop:
// The domainname has been seen, we at the start of the fixed part in the header.
// Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length.
off += 2 + 2 + 4
if off+1 > len(msg) {
return false
}
//off+1 is the end of the header, 'end' is the end of the rr
//so 'end' - 'off+2' is the lenght of the rdata
msg[off], msg[off+1] = packUint16(uint16(end - (off + 2)))
return true
return
}

48
tsig.go
View File

@ -82,34 +82,33 @@ type timerWireFmt struct {
Fudge uint16
}
// TsigGenerate adds an TSIG RR to a message. The message should contain
// a "stub" TsigRR with the algorithm, key name (owner name of the RR),
// time fudge (defaults to 300 seconds) and the current time
// The TSIG MAC is saved in that Tsig RR.
// When TsigGenerate is called for the
// first time requestMAC is set to the empty string.
// If something goes wrong an error is returned, otherwise it is nil.
// TODO this needs to work on []byte, not *Msg, to take
// compression into account
// This
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) error {
// TsigGenerate fills out the TSIG record attached to the message.
// The message should contain
// a "stub" TSIG RR with the algorithm, key name (owner name of the RR),
// time fudge (defaults to 300 seconds) and the current time
// The TSIG MAC is saved in that Tsig RR.
// When TsigGenerate is called for the first time requestMAC is set to the empty string and
// timersOnly is false.
// If something goes wrong an error is returned, otherwise it is nil.
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
if !m.IsTsig() {
// panic? panic?
panic("TSIG not last RR in additional")
}
// If we barf here, the caller is to blame
rawsecret, err := packBase64([]byte(secret))
if err != nil {
return err
return nil, "", err
}
rr := m.Extra[len(m.Extra)-1].(*RR_TSIG)
m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg
mbuf, _ := m.Pack()
mbuf, ok := m.Pack()
if !ok {
return nil, "", ErrPack
}
buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly)
t := new(RR_TSIG)
var h hash.Hash
switch rr.Algorithm {
case HmacMD5:
@ -119,10 +118,10 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) error {
case HmacSHA256:
h = hmac.New(sha256.New, []byte(rawsecret))
default:
return ErrKeyAlg
return nil, "", ErrKeyAlg
}
t.MAC = hex.EncodeToString(h.Sum(buf))
io.WriteString(h, string(buf))
t.MAC = hex.EncodeToString(h.Sum(nil))
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0}
@ -130,9 +129,16 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) error {
t.TimeSigned = rr.TimeSigned
t.Algorithm = rr.Algorithm
t.OrigId = m.MsgHdr.Id
m.Extra = append(m.Extra, t)
return nil
tbuf := make([]byte, t.Len())
if off, ok := packRR(t, tbuf, 0, nil, false); ok {
tbuf = tbuf[:off] // reset to actual size used
} else {
return nil, "", ErrPack
}
mbuf = append(mbuf, tbuf...)
RawSetExtraLen(mbuf, uint16(len(m.Extra)+1))
return mbuf, t.MAC, nil
}
// TsigVerify verifies the TSIG on a message.

View File

@ -178,7 +178,7 @@ func (rr *RR_ANY) Len() int {
}
type RR_CNAME struct {
Hdr RR_Header
Hdr RR_Header
Target string "cdomain-name"
}
@ -1096,7 +1096,7 @@ func dateToTime(s string) (uint32, error) {
// need for RFC1982 calculations as this date is 48 bits
func tsigTimeToDate(t uint64) string {
// only use the lower 48 bits, TODO(mg), check for 48 bit size
return ""
return "TODO"
/*
ti := time.Unix(int64(t), 0).Unix()
return ti.Format("20060102150405")

View File

@ -666,7 +666,7 @@ func setNSEC3(h RR_Header, c chan lex, o, f string) (RR, *ParseError) {
rr.TypeBitMap = make([]uint16, 0)
var (
k uint16
k uint16
ok bool
)
l = <-c
@ -676,7 +676,7 @@ func setNSEC3(h RR_Header, c chan lex, o, f string) (RR, *ParseError) {
// Ok
case _STRING:
if k, ok = Str_rr[strings.ToUpper(l.token)]; !ok {
if k, ok = typeToInt(l.token); ! ok {
if k, ok = typeToInt(l.token); !ok {
return nil, &ParseError{f, "bad NSEC3 TypeBitMap", l}
}
}