From 50564595a70f2c2ea0ad4a9b4b96e33bab6b3633 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Sat, 26 Mar 2022 15:52:05 +0100 Subject: add dns package Mostly imported from sigsum-log-go. Added strict domain hint parsing, two tests, and an example. This should be part of the sigsum-go lib because some client tooling may also want to check domain hints. --- pkg/dns/dns.go | 62 ++++++++++++++++++++++++++++++++ pkg/dns/dns_test.go | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 pkg/dns/dns.go create mode 100644 pkg/dns/dns_test.go diff --git a/pkg/dns/dns.go b/pkg/dns/dns.go new file mode 100644 index 0000000..3bc0583 --- /dev/null +++ b/pkg/dns/dns.go @@ -0,0 +1,62 @@ +// package dns checks if a domain name is aware of a hashed public key. A +// look-up is performed if the specified domain name matches "^_sigsum_v0.*". +package dns + +import ( + "context" + "fmt" + "net" + "strings" + + "git.sigsum.org/sigsum-lib-go/pkg/hex" + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +const ( + prefix = "_sigsum_v0." +) + +// Verifier can verify that a domain name is aware of a public key +type Verifier interface { + Verify(ctx context.Context, name string, key *types.PublicKey) error +} + +// DefaultResolver implements the Verifier interface with Go's default resolver +type DefaultResolver struct { + resolver net.Resolver +} + +func NewDefaultResolver() Verifier { + return &DefaultResolver{} +} + +func (dr *DefaultResolver) Verify(ctx context.Context, name string, pub *types.PublicKey) error { + if err := validPrefix(name); err != nil { + return fmt.Errorf("dns: %s", err) + } + rsps, err := dr.resolver.LookupTXT(ctx, name) + if err != nil { + return fmt.Errorf("dns: look-up failed: %s", name) + } + if err := validResponse(pub, rsps); err != nil { + return fmt.Errorf("dns: %s", err) + } + return nil +} + +func validResponse(pub *types.PublicKey, rsps []string) error { + keyHash := hex.Serialize(types.HashFn(pub[:])[:]) + for _, rsp := range rsps { + if rsp == keyHash { + return nil + } + } + return fmt.Errorf("unknown key hash %s", keyHash) +} + +func validPrefix(name string) error { + if !strings.HasPrefix(name, prefix) { + return fmt.Errorf("domain name prefix must be %s", prefix) + } + return nil +} diff --git a/pkg/dns/dns_test.go b/pkg/dns/dns_test.go new file mode 100644 index 0000000..e8e2c01 --- /dev/null +++ b/pkg/dns/dns_test.go @@ -0,0 +1,102 @@ +package dns + +import ( + "context" + "log" + "testing" + "time" + + "git.sigsum.org/sigsum-lib-go/pkg/hex" + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +func Example() { + name := "_sigsum_v0.testonly.sigsum.org" + pub := mustDecodePublicKey("cda2517e17dcba133eb0e71bf77473f94a77d7e61b1de4e1e64adfd0938d6182") + + timeout := 10 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + dr := NewDefaultResolver() + if err := dr.Verify(ctx, name, pub); err != nil { + log.Fatal(err.Error()) + } + + // Output: +} + +func TestValidResponse(t *testing.T) { + pub := mustDecodePublicKey("cda2517e17dcba133eb0e71bf77473f94a77d7e61b1de4e1e64adfd0938d6182") + for _, table := range []struct { + desc string + rsps []string + wantOK bool + }{ + { + desc: "invalid: upper-case hex-encoding", + rsps: []string{ + "", + "abc", + "C522D929B261241EEF174B51B8472FA5D5F961892089A7B85FD25CE73271ABCA", + "defghi", + }, + }, + { + desc: "valid", + rsps: []string{ + "", + "abc", + "c522d929b261241eef174b51b8472fa5d5f961892089a7b85fd25ce73271abca", + "defghi", + }, + wantOK: true, + }, + } { + err := validResponse(pub, table.rsps) + if got, want := err == nil, table.wantOK; got != want { + t.Errorf("got error but wanted none in test %q: %v", table.desc, err) + } + } +} + +func TestValidPrefix(t *testing.T) { + for _, table := range []struct { + desc string + name string + wantOK bool + }{ + { + desc: "invalid: bad prefix (1/2)", + name: "x_sigsum_v0.sigsum.org", + }, + { + desc: "invalid: bad prefix (2/2)", + name: "_sigsum_v0x.sigsum.org", + }, + { + desc: "valid", + name: "_sigsum_v0.sigsum.org", + wantOK: true, + }, + } { + err := validPrefix(table.name) + if got, want := err == nil, table.wantOK; got != want { + t.Errorf("got error but wanted none in test %q: %v", table.desc, err) + } + } +} + +func mustDecodePublicKey(str string) *types.PublicKey { + b, err := hex.Deserialize(str) + if err != nil { + log.Fatal(err.Error()) + } + if len(b) != types.PublicKeySize { + log.Fatal("invalid key size") + } + + var pub types.PublicKey + copy(pub[:], b) + return &pub +} -- cgit v1.2.3