Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make TsigVerify check time after signature per rfc2845bis #1135

Merged
merged 3 commits into from Jul 18, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 18 additions & 11 deletions tsig.go
Expand Up @@ -153,6 +153,11 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, s
// If the signature does not validate err contains the
// error, otherwise it is nil.
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
return tsigVerify(msg, secret, requestMAC, timersOnly, uint64(time.Now().Unix()))
}

// actual implementation of TsigVerify, taking the current time ('now') as a parameter for the convenience of tests.
func tsigVerify(msg []byte, secret, requestMAC string, timersOnly bool, now uint64) error {
rawsecret, err := fromBase64([]byte(secret))
if err != nil {
return err
Expand All @@ -170,17 +175,6 @@ func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {

buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly)

// Fudge factor works both ways. A message can arrive before it was signed because
// of clock skew.
now := uint64(time.Now().Unix())
ti := now - tsig.TimeSigned
if now < tsig.TimeSigned {
ti = tsig.TimeSigned - now
}
if uint64(tsig.Fudge) < ti {
return ErrTime
}

var h hash.Hash
switch CanonicalName(tsig.Algorithm) {
case HmacMD5:
Expand All @@ -198,6 +192,19 @@ func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
if !hmac.Equal(h.Sum(nil), msgMAC) {
return ErrSig
}

// Fudge factor works both ways. A message can arrive before it was signed because
// of clock skew.
// We check this after verifying the signature, following draft-ietf-dnsop-rfc2845bis
// instead of RFC2845, in order to prevent a security vulnerability as reported in CVE-2017-3142/3143.
ti := now - tsig.TimeSigned
if now < tsig.TimeSigned {
ti = tsig.TimeSigned - now
}
if uint64(tsig.Fudge) < ti {
return ErrTime
}

return nil
}

Expand Down
57 changes: 57 additions & 0 deletions tsig_test.go
Expand Up @@ -2,6 +2,8 @@ package dns

import (
"encoding/binary"
"encoding/hex"
"fmt"
"testing"
"time"
)
Expand Down Expand Up @@ -50,3 +52,58 @@ func TestTsigCase(t *testing.T) {
t.Fatal(err)
}
}

const (
// A template wire-format DNS message (in hex form) containing a TSIG RR.
// Its time signed field will be filled by tests.
wireMsg = "c60028000001000000010001076578616d706c6503636f6d00000600010161c00c0001000100000e100004c0000201077465" +
"73746b65790000fa00ff00000000003d0b686d61632d73686132353600" +
"%012x" + // placeholder for the "time signed" field
"012c00208cf23e0081d915478a182edcea7ff48ad102948e6c7ef8e887536957d1fa5616c60000000000"
// A secret (in base64 format) with which the TSIG in wireMsg will be validated
testSecret = "NoTCJU+DMqFWywaPyxSijrDEA/eC3nK0xi3AMEZuPVk="
// the 'time signed' field value that would make the TSIG RR valid with testSecret
timeSigned uint64 = 1594855491
)

func TestTsigErrors(t *testing.T) {
// Helper shortcut to build wire-format test message.
// TsigVerify can modify the slice, so we need to create a new one for each test case below.
buildMsgData := func(tm uint64) []byte {
msgData, err := hex.DecodeString(fmt.Sprintf(wireMsg, tm))
if err != nil {
t.Fatal(err)
}
return msgData
}

checkError := func(expected, actual error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditch this because it doesn’t match the rest of the tests and just include only the if actual != expected check directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean instead of

checkError(ErrTime, tsigVerify(buildMsgData(timeSigned), testSecret, "", false, timeSigned+301))

do something like this?

	if err := tsigVerify(buildMsgData(timeSigned), testSecret, "", false, timeSigned+301); err != ErrTime {
		t.Fatalf("expected an error '%v' but got '%v'", ErrTime, err)
	}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jinmeiib Exactly that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tmthrgd okay, updated as such.

if actual == nil {
t.Fatal("expected an error, but got nil")
}
if actual != expected {
t.Fatalf("expected an error '%v' but got '%v'", expected, actual)
}
}

// the signature is valid but 'time signed' is too far from the "current time".
checkError(ErrTime, tsigVerify(buildMsgData(timeSigned), testSecret, "", false, timeSigned+301))
checkError(ErrTime, tsigVerify(buildMsgData(timeSigned), testSecret, "", false, timeSigned-301))

// the signature is invalid and 'time signed' is too far.
// the signature should be checked first, so we should see ErrSig.
checkError(ErrSig, tsigVerify(buildMsgData(timeSigned+301), testSecret, "", false, timeSigned))

// tweak the algorithm name in the wire data, resulting in the "unknown algorithm" error.
msgData := buildMsgData(timeSigned)
garbage := []byte("bogus")
copy(msgData[67:67+len(garbage)], garbage)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be: copy(msgData[67:], "bogus").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, updated it as suggested.

checkError(ErrKeyAlg, tsigVerify(msgData, testSecret, "", false, timeSigned))

// call TsigVerify with a message that doesn't contain a TSIG
msgData, _, err := stripTsig(buildMsgData(timeSigned))
if err != nil {
t.Fatal(err)
}
checkError(ErrNoSig, tsigVerify(msgData, testSecret, "", false, timeSigned))
}