pax_global_header00006660000000000000000000000064152070604510014512gustar00rootroot0000000000000052 comment=550b1c37e530bc24c12778895128c46a40fe903c golang-github-bodgit-tsig-1.3.1/000077500000000000000000000000001520706045100164555ustar00rootroot00000000000000golang-github-bodgit-tsig-1.3.1/.github/000077500000000000000000000000001520706045100200155ustar00rootroot00000000000000golang-github-bodgit-tsig-1.3.1/.github/renovate.json000066400000000000000000000003441520706045100225340ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended", "docker:pinDigests" ], "pre-commit": { "enabled": true }, "postUpdateOptions": [ "gomodTidy" ] } golang-github-bodgit-tsig-1.3.1/.github/workflows/000077500000000000000000000000001520706045100220525ustar00rootroot00000000000000golang-github-bodgit-tsig-1.3.1/.github/workflows/build.yml000066400000000000000000000103011520706045100236670ustar00rootroot00000000000000name: Build on: push: branches: - main pull_request: branches: - main workflow_dispatch: schedule: - cron: 0 0 * * 1 jobs: test: name: Build and Test runs-on: ubuntu-latest env: DNS_HOST: ns.example.com DNS_PORT: 8053 DNS_REALM: EXAMPLE.COM DNS_USERNAME: test DNS_PASSWORD: password DNS_KEYTAB: ${{ github.workspace }}/testdata/test.keytab KRB5_CONFIG: ${{ github.workspace }}/testdata/krb5.conf KRB5_KTNAME: ${{ github.workspace }}/testdata/dns.keytab steps: - name: Checkout uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: 'go.mod' - name: Install Kerberos client run: | sudo apt-get update sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq libkrb5-dev krb5-user - name: golangci-lint (gokrb5) uses: golangci/golangci-lint-action@v9 if: github.event_name == 'pull_request' with: only-new-issues: true - name: golangci-lint (apcera) uses: golangci/golangci-lint-action@v9 if: github.event_name == 'pull_request' with: only-new-issues: true args: --build-tags apcera - name: golangci-lint (SSPI) uses: golangci/golangci-lint-action@v9 if: github.event_name == 'pull_request' with: only-new-issues: true env: GOOS: windows - name: Podman version id: podman shell: bash run: | echo "version=$(podman version | grep '^Version:' | tr -s ' ' | cut -d ' ' -f 2)" >>"${GITHUB_OUTPUT}" - name: Downgrade Docker if: steps.podman.outputs.version == '3.4.4' shell: bash run: | apt-cache madison docker.io sudo apt-get remove containerd.io sudo apt-get install docker.io=24.0.7-0ubuntu2~22.04.1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Build KDC image uses: docker/build-push-action@v6 with: context: "{{defaultContext}}:testdata" load: true tags: kdc target: kdc cache-from: type=gha cache-to: type=gha,mode=max - name: Build DNS image uses: docker/build-push-action@v6 with: context: "{{defaultContext}}:testdata" load: true tags: ns target: ns cache-from: type=gha cache-to: type=gha,mode=max - name: Extract keytab uses: docker/build-push-action@v6 with: context: "{{defaultContext}}:testdata" outputs: type=local,dest=testdata target: keytab - name: Pull containers into Podman run: | podman pull docker-daemon:kdc:latest podman pull docker-daemon:ns:latest - name: Create infrastructure run: | podman run -d \ -v /etc/localtime:/etc/localtime:ro \ -p 127.0.0.1:8088:8088 \ -p 127.0.0.1:8088:8088/udp \ -p 127.0.0.1:8464:8464 \ -p 127.0.0.1:8464:8464/udp \ --name kdc kdc podman run -d \ -v /etc/localtime:/etc/localtime:ro \ -p 127.0.0.1:${DNS_PORT}:${DNS_PORT} \ --name ns --hostname $DNS_HOST ns echo 127.0.0.1 $DNS_HOST | sudo tee -a /etc/hosts echo $DNS_PASSWORD | KRB5_TRACE=/dev/stdout kinit ${DNS_USERNAME}@${DNS_REALM} - name: Test (gokrb5) run: go test -v -coverprofile=gokrb5.out ./... - name: Test (apcera) run: go test -v -coverprofile=apcera.out -tags apcera ./... - name: Build (SSPI) run: go build ./... env: GOARCH: amd64 GOOS: windows - name: Install coverage tools run: | go install github.com/wadey/gocovmerge@latest go install github.com/mattn/goveralls@latest - name: Merge coverage reports run: gocovmerge gokrb5.out apcera.out >cover.out - name: Send coverage run: goveralls -coverprofile=cover.out -service=github env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} golang-github-bodgit-tsig-1.3.1/.github/workflows/cleanup.yml000066400000000000000000000015201520706045100242220ustar00rootroot00000000000000name: Clean up per-branch caches on: pull_request: types: - closed workflow_dispatch: jobs: cleanup: runs-on: ubuntu-latest permissions: actions: write contents: read steps: - name: Checkout uses: actions/checkout@v6 - name: Cleanup run: | gh extension install actions/gh-actions-cache BRANCH=refs/pull/${{ github.event.pull_request.number }}/merge echo "Fetching list of cache keys" keys=$(gh actions-cache list -R $GITHUB_REPOSITORY -B $BRANCH -L 100 | cut -f 1) set +e echo "Deleting caches..." for key in $keys ; do gh actions-cache delete $key -R $GITHUB_REPOSITORY -B $BRANCH --confirm done echo "Done" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} golang-github-bodgit-tsig-1.3.1/.github/workflows/pr-lint.yml000066400000000000000000000022321520706045100241610ustar00rootroot00000000000000name: Lint pull request on: pull_request: types: - opened - edited - synchronize jobs: main: name: Validate PR title runs-on: ubuntu-latest permissions: pull-requests: write steps: - uses: amannn/action-semantic-pull-request@v6 id: lint_pr_title env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: marocchino/sticky-pull-request-comment@v3.0.4 if: always() && steps.lint_pr_title.outputs.error_message != null with: header: pr-title-lint-error message: | Hey there and thank you for opening this pull request! 👋🏼 We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted. Details: ``` ${{ steps.lint_pr_title.outputs.error_message }} ``` - uses: marocchino/sticky-pull-request-comment@v3.0.4 if: steps.lint_pr_title.outputs.error_message == null with: header: pr-title-lint-error delete: true golang-github-bodgit-tsig-1.3.1/.github/workflows/release.yml000066400000000000000000000005001520706045100242100ustar00rootroot00000000000000--- name: Track releases on: push: branches: - main permissions: {} jobs: release-please: name: Run release-please uses: bodgit/workflows/.github/workflows/release.yml@90676a57fa5e7bb53dbea051211751b225ee60ca # v1.0.1 secrets: RELEASE_PLEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }} golang-github-bodgit-tsig-1.3.1/.gitignore000066400000000000000000000000171520706045100204430ustar00rootroot00000000000000*.out *.keytab golang-github-bodgit-tsig-1.3.1/.golangci.yaml000066400000000000000000000036541520706045100212120ustar00rootroot00000000000000version: "2" linters: default: none enable: - asasalint - asciicheck - bidichk - bodyclose - canonicalheader - containedctx - contextcheck #- copyloopvar - cyclop - decorder - dogsled - dupl - dupword - durationcheck #- err113 - errcheck - errchkjson - errname - errorlint - exhaustive - exptostd - fatcontext - forbidigo - forcetypeassert #- funcorder - funlen - ginkgolinter - gocheckcompilerdirectives - gochecknoglobals - gochecknoinits - gochecksumtype - gocognit - goconst - gocritic - gocyclo - godot #- godox - goheader - gomoddirectives - gomodguard - goprintffuncname #- gosec - gosmopolitan #- govet - grouper - iface - importas #- inamedparam - ineffassign - interfacebloat #- intrange - lll - loggercheck - maintidx - makezero - mirror - misspell - musttag - nakedret - nestif - nilerr - nilnesserr - nilnil - nlreturn - noctx - nolintlint #- nonamedreturns - nosprintfhostport - paralleltest #- perfsprint - prealloc - predeclared - promlinter - protogetter - reassign - recvcheck #- revive - rowserrcheck - sloglint - spancheck - sqlclosecheck - staticcheck - tagalign - tagliatelle - testableexamples #- testifylint - testpackage - thelper - tparallel - unconvert - unparam - unused - usestdlibvars - usetesting - wastedassign - whitespace #- wrapcheck #- wsl - zerologlint exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ formatters: enable: - gci - gofmt - gofumpt - goimports exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ golang-github-bodgit-tsig-1.3.1/.pre-commit-config.yaml000066400000000000000000000003351520706045100227370ustar00rootroot00000000000000repos: - repo: https://github.com/commitizen-tools/commitizen rev: v4.16.2 hooks: - id: commitizen - repo: https://github.com/golangci/golangci-lint rev: v2.12.2 hooks: - id: golangci-lint golang-github-bodgit-tsig-1.3.1/.release-please-manifest.json000066400000000000000000000000231520706045100241140ustar00rootroot00000000000000{ ".": "1.3.1" } golang-github-bodgit-tsig-1.3.1/CHANGELOG.md000066400000000000000000000036701520706045100202740ustar00rootroot00000000000000# Changelog ## [1.3.1](https://github.com/bodgit/tsig/compare/v1.3.0...v1.3.1) (2026-05-20) ### Bug Fixes * **deps:** update module github.com/bodgit/gssapi to v0.0.3 ([#179](https://github.com/bodgit/tsig/issues/179)) ([0823be3](https://github.com/bodgit/tsig/commit/0823be329768432679799b985f0e8a8254941278)) ## [1.3.0](https://github.com/bodgit/tsig/compare/v1.2.2...v1.3.0) (2026-04-28) ### Features * replace copier with shallow copy ([#154](https://github.com/bodgit/tsig/issues/154)) ([6d7668b](https://github.com/bodgit/tsig/commit/6d7668b06318e4c9a6fe349d11d530721109de2b)) ### Bug Fixes * **deps:** update github.com/alexbrainman/sspi digest to 7d374ff ([#162](https://github.com/bodgit/tsig/issues/162)) ([3e8f387](https://github.com/bodgit/tsig/commit/3e8f3876600dfba9e25edfca4a9f173d456ee2d5)) * **deps:** update module github.com/go-logr/logr to v1.4.3 ([#163](https://github.com/bodgit/tsig/issues/163)) ([a6a9ef2](https://github.com/bodgit/tsig/commit/a6a9ef2434a3ccd5858d0fd80813bba44119632b)) * **deps:** update module github.com/miekg/dns to v1.1.72 ([#164](https://github.com/bodgit/tsig/issues/164)) ([a26c6e9](https://github.com/bodgit/tsig/commit/a26c6e9c9d13e458b8c4263a182e85e7b5d76a2b)) * **deps:** update module github.com/stretchr/testify to v1.11.1 ([#165](https://github.com/bodgit/tsig/issues/165)) ([a9acd25](https://github.com/bodgit/tsig/commit/a9acd258b254a361faabc0b587cfba168aee64e2)) * Refactor to use GSSAPI wrapper ([d62bcde](https://github.com/bodgit/tsig/commit/d62bcded8ca4bf496b9b34a1eff391a80a0a6fed)) * Refactor to use GSSAPI wrapper ([504a37f](https://github.com/bodgit/tsig/commit/504a37fef2a82af0600d95e1251c29a7d06fadec)), closes [#111](https://github.com/bodgit/tsig/issues/111) * Use expiry from negotiated context ([#121](https://github.com/bodgit/tsig/issues/121)) ([0d4e7fc](https://github.com/bodgit/tsig/commit/0d4e7fc345691cb63845ddd2846e793ae763426a)), closes [#6](https://github.com/bodgit/tsig/issues/6) golang-github-bodgit-tsig-1.3.1/LICENSE000066400000000000000000000027501520706045100174660ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2018, Matt Dainty All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golang-github-bodgit-tsig-1.3.1/README.md000066400000000000000000000103121520706045100177310ustar00rootroot00000000000000[![GitHub release](https://img.shields.io/github/v/release/bodgit/tsig)](https://github.com/bodgit/tsig/releases) [![Build Status](https://img.shields.io/github/actions/workflow/status/bodgit/tsig/build.yml?branch=main)](https://github.com/bodgit/tsig/actions?query=workflow%3ABuild) [![Coverage Status](https://coveralls.io/repos/github/bodgit/tsig/badge.svg?branch=main)](https://coveralls.io/github/bodgit/tsig?branch=main) [![Go Report Card](https://goreportcard.com/badge/github.com/bodgit/tsig)](https://goreportcard.com/report/github.com/bodgit/tsig) [![GoDoc](https://godoc.org/github.com/bodgit/tsig?status.svg)](https://godoc.org/github.com/bodgit/tsig) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/bodgit/tsig) # Additional TSIG methods The [github.com/bodgit/tsig](https://godoc.org/github.com/bodgit/tsig) package adds support for additional TSIG methods used in DNS queries. It is designed to be used alongside the [github.com/miekg/dns](https://github.com/miekg/dns) package which is used to construct and parse DNS queries and responses. This is most useful for allowing [RFC 3645 GSS-TSIG](https://www.ietf.org/rfc/rfc3645.txt) which is necessary for dealing with Windows DNS servers that require 'Secure only' updates or BIND if it has been configured to use Kerberos. ## Notable users * [https://github.com/hashicorp/terraform-provider-dns](https://github.com/hashicorp/terraform-provider-dns) * [https://github.com/go-acme/lego](https://github.com/go-acme/lego) * [https://github.com/kubernetes-sigs/external-dns](https://github.com/kubernetes-sigs/external-dns) ## Usage > :warning: Windows DNS servers don't accept wildcard resource names and only a subset of record types in dynamic updates. Here is an example client, it is necessary that your Kerberos or Active Directory environment is configured and functional: ```golang package main import ( "fmt" "time" "github.com/bodgit/tsig" "github.com/bodgit/tsig/gss" "github.com/miekg/dns" ) func main() { dnsClient := new(dns.Client) dnsClient.Net = "tcp" gssClient, err := gss.NewClient(dnsClient) if err != nil { panic(err) } defer gssClient.Close() host := "ns.example.com:53" // Negotiate a context with the chosen server using the // current user. See also gssClient.NegotiateContextWithCredentials() // and gssClient.NegotiateContextWithKeytab() for alternatives keyname, _, err := gssClient.NegotiateContext(host) if err != nil { panic(err) } dnsClient.TsigProvider = gssClient // Use the DNS client as normal msg := new(dns.Msg) msg.SetUpdate(dns.Fqdn("example.com")) insert, err := dns.NewRR("test.example.com. 300 A 192.0.2.1") if err != nil { panic(err) } msg.Insert([]dns.RR{insert}) msg.SetTsig(keyname, tsig.GSS, 300, time.Now().Unix()) rr, _, err := dnsClient.Exchange(msg, host) if err != nil { panic(err) } if rr.Rcode != dns.RcodeSuccess { fmt.Printf("DNS error: %s (%d)\n", dns.RcodeToString[rr.Rcode], rr.Rcode) } // Cleanup the context err = gssClient.DeleteContext(keyname) if err != nil { panic(err) } } ``` If you need to deal with both regular TSIG and GSS-TSIG together then this package also exports an HMAC TSIG implementation. To use both together set your client up something like this: ```golang package main import ( "github.com/bodgit/tsig" "github.com/bodgit/tsig/gss" "github.com/miekg/dns" ) func main() { dnsClient := new(dns.Client) dnsClient.Net = "tcp" // Create HMAC TSIG provider hmac := tsig.HMAC{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="} // Create GSS-TSIG provider gssClient, err := gss.NewClient(dnsClient) if err != nil { panic(err) } defer gssClient.Close() // Configure DNS client with both providers dnsClient.TsigProvider = tsig.MultiProvider(hmac, gssClient) // Use the DNS client as normal } ``` golang-github-bodgit-tsig-1.3.1/dh/000077500000000000000000000000001520706045100170505ustar00rootroot00000000000000golang-github-bodgit-tsig-1.3.1/dh/dh.go000066400000000000000000000173511520706045100200010ustar00rootroot00000000000000/* Package dh implements RFC 2930 Diffie-Hellman key exchange functions. Example client: import ( "fmt" "time" "github.com/bodgit/tsig/dh" "github.com/miekg/dns" ) func main() { dnsClient := new(dns.Client) dnsClient.Net = "tcp" dnsClient.TsigSecret = map[string]string{"tsig.example.com.": "k9uK5qsPfbBxvVuldwzYww=="} dhClient, err := dh.NewClient(dnsClient) if err != nil { panic(err) } defer dhClient.Close() host := "ns.example.com:53" // Negotiate a key with the chosen server keyname, mac, _, err := dhClient.NegotiateKey(host, "tsig.example.com.", dns.HmacMD5, "k9uK5qsPfbBxvVuldwzYww==") if err != nil { panic(err) } dnsClient.TsigSecret[keyname] = mac // Use the DNS client as normal msg := new(dns.Msg) msg.SetUpdate(dns.Fqdn("example.com")) insert, err := dns.NewRR("test.example.com. 300 A 192.0.2.1") if err != nil { panic(err) } msg.Insert([]dns.RR{insert}) msg.SetTsig(keyname, dns.HmacMD5, 300, time.Now().Unix()) rr, _, err := dnsClient.Exchange(msg, host) if err != nil { panic(err) } if rr.Rcode != dns.RcodeSuccess { fmt.Printf("DNS error: %s (%d)\n", dns.RcodeToString[rr.Rcode], rr.Rcode) } // Revoke the key err = dhClient.DeleteKey(keyname) if err != nil { panic(err) } } */ package dh import ( "bytes" "crypto/md5" //nolint:gosec "crypto/rand" "encoding/base64" "encoding/binary" "encoding/hex" "errors" "fmt" "io" "math/big" "strings" "sync" "time" "github.com/bodgit/tsig/internal/util" "github.com/enceve/crypto/dh" multierror "github.com/hashicorp/go-multierror" "github.com/miekg/dns" ) const ( // RFC 2409, section 6.2. modp1024 = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + "FFFFFFFFFFFFFFFF" ) type context struct { host, algorithm, mac string } type dhkey struct { prime, generator, key []byte } // Client maps the TKEY name to the target host that negotiated it as // well as any other internal state. type Client struct { m sync.Mutex client *dns.Client ctx map[string]*context } func dhGroup(group int) (*dh.Group, error) { switch group { case 2: p, _ := new(big.Int).SetString(modp1024, 16) return &dh.Group{ P: p, G: new(big.Int).SetInt64(2), }, nil default: return nil, fmt.Errorf("unsupported DH group %v", group) } } // NewClient performs any library initialization necessary. // It returns a context handle for any further functions along with any error // that occurred. func NewClient(dnsClient *dns.Client) (*Client, error) { client, err := util.CopyDNSClient(dnsClient) if err != nil { return nil, err } c := &Client{ client: client, ctx: make(map[string]*context), } return c, nil } // Close revokes any active keys and unloads any underlying libraries as // necessary. // It returns any error that occurred. func (c *Client) Close() error { c.m.Lock() keys := make([]string, 0, len(c.ctx)) for k := range c.ctx { keys = append(keys, k) } c.m.Unlock() var errs *multierror.Error for _, k := range keys { errs = multierror.Append(errs, c.DeleteKey(k)) } return errs.ErrorOrNil() } func readDHKey(raw []byte) (*dhkey, error) { var key dhkey r := bytes.NewBuffer(raw) var l uint16 for _, f := range []*[]byte{&key.prime, &key.generator, &key.key} { err := binary.Read(r, binary.BigEndian, &l) if err != nil { return nil, err } *f = make([]byte, l) if _, err = io.ReadFull(r, *f); err != nil { return nil, err } } return &key, nil } func writeDHKey(key *dhkey) ([]byte, error) { w := new(bytes.Buffer) for _, f := range []*[]byte{&key.prime, &key.generator, &key.key} { l := uint16(len(*f)) err := binary.Write(w, binary.BigEndian, l) if err != nil { return nil, err } if _, err = w.Write(*f); err != nil { return nil, err } } return w.Bytes(), nil } func computeMD5(nonce, secret []byte) []byte { //nolint:gosec checksum := md5.Sum(append(nonce, secret...)) return checksum[:] } func computeDHKey(ourNonce, peerNonce, secret []byte) []byte { operand := append(computeMD5(ourNonce, secret), computeMD5(peerNonce, secret)...) var result []byte if len(secret) > len(operand) { result = make([]byte, len(secret)) copy(result, secret) for i := 0; i < len(operand); i++ { result[i] ^= operand[i] } } else { result = make([]byte, len(operand)) copy(result, operand) for i := 0; i < len(secret); i++ { result[i] ^= secret[i] } } return result } // NegotiateKey exchanges RFC 2930 TKEY records with the indicated DNS // server to establish a TSIG key for further using an existing TSIG key name, // algorithm and MAC. // It returns the negotiated TKEY name, MAC, expiry time, and any error that // occurred. // //nolint:cyclop,funlen func (c *Client) NegotiateKey(host, name, algorithm, mac string) (string, string, time.Time, error) { keyname := "." g, err := dhGroup(2) if err != nil { return "", "", time.Time{}, err } ax, ay, err := g.GenerateKey(nil) if err != nil { return "", "", time.Time{}, err } adh := &dhkey{ prime: g.P.Bytes(), generator: g.G.Bytes(), key: (*big.Int)(ay).Bytes(), } akey, err := writeDHKey(adh) if err != nil { return "", "", time.Time{}, err } // Generate our nonce an := make([]byte, 16) // FIXME I suspect it just is if _, err = rand.Read(an); err != nil { return "", "", time.Time{}, err } extra := make([]dns.RR, 1) extra[0] = &dns.DNSKEY{ Hdr: dns.RR_Header{ Name: keyname, Rrtype: dns.TypeKEY, Class: dns.ClassANY, Ttl: 0, }, Flags: 512, // FIXME Protocol: 3, // FIXME Algorithm: dns.DH, PublicKey: base64.StdEncoding.EncodeToString(akey), } c.client.TsigSecret[name] = mac defer delete(c.client.TsigSecret, name) //nolint:lll tkey, keys, err := util.ExchangeTKEY(c.client, host, keyname, dns.HmacMD5, util.TkeyModeDH, 3600, an, extra, name, algorithm) if err != nil { return "", "", time.Time{}, err } var bkey []byte for _, k := range keys { if key, ok := k.(*dns.KEY); ok { if key.Header().Name != keyname && key.Algorithm == dns.DH { if bkey, err = base64.StdEncoding.DecodeString(key.PublicKey); err != nil { return "", "", time.Time{}, err } } } } if bkey == nil { return "", "", time.Time{}, errors.New("no peer KEY record") } bdh, err := readDHKey(bkey) if err != nil { return "", "", time.Time{}, err } by := new(big.Int).SetBytes(bdh.key) err = g.Check(by) if err != nil { return "", "", time.Time{}, err } secret := g.ComputeSecret(ax, by).Bytes() // The peer nonce is in the TKEY response bn, err := hex.DecodeString(tkey.Key) if err != nil { return "", "", time.Time{}, err } lower := strings.ToLower(tkey.Header().Name) key := base64.StdEncoding.EncodeToString(computeDHKey(an, bn, secret)) expiry := time.Unix(int64(tkey.Expiration), 0) c.m.Lock() defer c.m.Unlock() c.ctx[lower] = &context{ host: host, algorithm: dns.HmacMD5, mac: key, } return lower, key, expiry, nil } // DeleteKey revokes the active key associated with the given TKEY name. // It returns any error that occurred. func (c *Client) DeleteKey(keyname string) error { c.m.Lock() defer c.m.Unlock() ctx, ok := c.ctx[keyname] if !ok { return errors.New("no such context") } c.client.TsigSecret[keyname] = ctx.mac defer delete(c.client.TsigSecret, keyname) // Delete the key, signing the query with the key itself // //nolint:lll if _, _, err := util.ExchangeTKEY(c.client, ctx.host, keyname, ctx.algorithm, util.TkeyModeDelete, 0, nil, nil, keyname, ctx.algorithm); err != nil { return err } delete(c.ctx, keyname) return nil } golang-github-bodgit-tsig-1.3.1/go.mod000066400000000000000000000023711520706045100175660ustar00rootroot00000000000000module github.com/bodgit/tsig go 1.25 require ( github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e github.com/bodgit/gssapi v0.0.3 github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815 github.com/go-logr/logr v1.4.3 github.com/hashicorp/go-multierror v1.1.1 github.com/jcmturner/gokrb5/v8 v8.4.4 github.com/miekg/dns v1.1.72 github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b github.com/stretchr/testify v1.11.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect github.com/jcmturner/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.15.0 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/mod v0.31.0 // indirect golang.org/x/net v0.48.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect golang.org/x/tools v0.40.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-bodgit-tsig-1.3.1/go.sum000066400000000000000000000222361520706045100176150ustar00rootroot00000000000000github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/bodgit/gssapi v0.0.3 h1:CtNl14kFo6aQE4tld//yBZ4xgJIPW32YxlOob0TG1y8= github.com/bodgit/gssapi v0.0.3/go.mod h1:DXyzvSyncJX6mT8WYYWYGzO/SrT+ijMi1ebfQHsWMNo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815 h1:D22EM5TeYZJp43hGDx6dUng8mvtyYbB9BnE3+BmJR1Q= github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815/go.mod h1:wYFFK4LYXbX7j+76mOq7aiC/EAw2S22CrzPHqgsisPw= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b h1:it0YPE/evO6/m8t8wxis9KFI2F/aleOKsI6d9uz0cEk= github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b/go.mod h1:tNrEB5k8SI+g5kOlsCmL2ELASfpqEofI0+FLBgBdN08= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-bodgit-tsig-1.3.1/gss/000077500000000000000000000000001520706045100172515ustar00rootroot00000000000000golang-github-bodgit-tsig-1.3.1/gss/apcera.go000066400000000000000000000143341520706045100210400ustar00rootroot00000000000000//go:build !windows && apcera // +build !windows,apcera package gss import ( "encoding/hex" "net" "sync" "time" "github.com/bodgit/tsig" "github.com/bodgit/tsig/internal/util" "github.com/go-logr/logr" multierror "github.com/hashicorp/go-multierror" "github.com/miekg/dns" "github.com/openshift/gssapi" ) // Client maps the TKEY name to the context that negotiated it as // well as any other internal state. type Client struct { m sync.RWMutex lib *gssapi.Lib client *dns.Client ctx map[string]*gssapi.CtxId logger logr.Logger } // WithConfig sets the Kerberos configuration used. func WithConfig(_ string) func(*Client) error { return func(c *Client) error { return errNotSupported } } // NewClient performs any library initialization necessary. // It returns a context handle for any further functions along with any error // that occurred. func NewClient(dnsClient *dns.Client, options ...func(*Client) error) (*Client, error) { client, err := util.CopyDNSClient(dnsClient) if err != nil { return nil, err } client.TsigProvider = new(gssNoVerify) lib, err := gssapi.Load(nil) if err != nil { return nil, err } c := &Client{ lib: lib, client: client, ctx: make(map[string]*gssapi.CtxId), logger: logr.Discard(), } if err := c.setOption(options...); err != nil { return nil, multierror.Append(err, c.lib.Unload()) } return c, nil } // Close deletes any active contexts and unloads any underlying libraries as // necessary. // It returns any error that occurred. func (c *Client) Close() error { return multierror.Append(c.close(), c.lib.Unload()) } func (c *Client) generate(ctx *gssapi.CtxId, msg []byte) ([]byte, error) { message, err := c.lib.MakeBufferBytes(msg) if err != nil { return nil, err } defer func() { err = multierror.Append(err, message.Release()).ErrorOrNil() }() token, err := ctx.GetMIC(gssapi.GSS_C_QOP_DEFAULT, message) if err != nil { return nil, err } defer func() { err = multierror.Append(err, token.Release()).ErrorOrNil() }() return token.Bytes(), nil } func (c *Client) verify(ctx *gssapi.CtxId, stripped, mac []byte) error { // Turn the TSIG-stripped message bytes into a *gssapi.Buffer message, err := c.lib.MakeBufferBytes(stripped) if err != nil { return err } defer func() { err = multierror.Append(err, message.Release()).ErrorOrNil() }() // Turn the TSIG MAC bytes into a *gssapi.Buffer token, err := c.lib.MakeBufferBytes(mac) if err != nil { return err } defer func() { err = multierror.Append(err, token.Release()).ErrorOrNil() }() // This is the actual verification bit if _, err = ctx.VerifyMIC(message, token); err != nil { return err } return nil } // NegotiateContext exchanges RFC 2930 TKEY records with the indicated DNS // server to establish a security context using the current user. // It returns the negotiated TKEY name, expiration time, and any error that // occurred. // //nolint:cyclop,funlen func (c *Client) NegotiateContext(host string) (keyname string, expiry time.Time, err error) { hostname, _, err := net.SplitHostPort(host) if err != nil { return "", time.Time{}, err } keyname, err = generateTKEYName(hostname) if err != nil { return "", time.Time{}, err } buffer, err := c.lib.MakeBufferString(generateSPN(hostname)) if err != nil { return "", time.Time{}, err } defer func() { err = multierror.Append(err, buffer.Release()).ErrorOrNil() }() service, err := buffer.Name(c.lib.GSS_KRB5_NT_PRINCIPAL_NAME) if err != nil { return "", time.Time{}, err } defer func() { err = multierror.Append(err, service.Release()).ErrorOrNil() }() var ( input *gssapi.Buffer ctx *gssapi.CtxId ) for ok := true; ok; ok = c.lib.LastStatus.Major.ContinueNeeded() { nctx, _, output, _, duration, err := c.lib.InitSecContext( c.lib.GSS_C_NO_CREDENTIAL, ctx, // nil initially service, c.lib.GSS_C_NO_OID, gssapi.GSS_C_MUTUAL_FLAG|gssapi.GSS_C_REPLAY_FLAG|gssapi.GSS_C_INTEG_FLAG, 0, c.lib.GSS_C_NO_CHANNEL_BINDINGS, input) ctx, expiry = nctx, time.Now().UTC().Add(duration) defer func() { err = multierror.Append(err, output.Release()).ErrorOrNil() }() if err != nil { if !c.lib.LastStatus.Major.ContinueNeeded() { return "", time.Time{}, err } } else { // There is no further token to send break } //nolint:lll tkey, _, err := util.ExchangeTKEY(c.client, host, keyname, tsig.GSS, util.TkeyModeGSS, 3600, output.Bytes(), nil, "", "") if err != nil { return "", time.Time{}, multierror.Append(err, ctx.DeleteSecContext()) } if tkey.Header().Name != keyname { return "", time.Time{}, multierror.Append(errDoesNotMatch, ctx.DeleteSecContext()) } key, err := hex.DecodeString(tkey.Key) if err != nil { return "", time.Time{}, multierror.Append(err, ctx.DeleteSecContext()) } if input, err = c.lib.MakeBufferBytes(key); err != nil { return "", time.Time{}, multierror.Append(err, ctx.DeleteSecContext()) } defer func() { err = multierror.Append(err, input.Release()).ErrorOrNil() }() } c.m.Lock() defer c.m.Unlock() c.ctx[keyname] = ctx return keyname, expiry, nil } // NegotiateContextWithCredentials exchanges RFC 2930 TKEY records with the // indicated DNS server to establish a security context using the provided // credentials. // It returns the negotiated TKEY name, expiration time, and any error that // occurred. func (c *Client) NegotiateContextWithCredentials(_, _, _, _ string) (string, time.Time, error) { return "", time.Time{}, errNotSupported } // NegotiateContextWithKeytab exchanges RFC 2930 TKEY records with the // indicated DNS server to establish a security context using the provided // keytab. // It returns the negotiated TKEY name, expiration time, and any error that // occurred. func (c *Client) NegotiateContextWithKeytab(_, _, _, _ string) (string, time.Time, error) { return "", time.Time{}, errNotSupported } // DeleteContext deletes the active security context associated with the given // TKEY name. // It returns any error that occurred. func (c *Client) DeleteContext(keyname string) error { c.m.Lock() defer c.m.Unlock() ctx, ok := c.ctx[keyname] if !ok { return errNoSuchContext } if err := ctx.DeleteSecContext(); err != nil { return err } delete(c.ctx, keyname) return nil } golang-github-bodgit-tsig-1.3.1/gss/apcera_test.go000066400000000000000000000011161520706045100220710ustar00rootroot00000000000000//go:build !windows && apcera // +build !windows,apcera package gss_test import ( "testing" "github.com/bodgit/tsig/gss" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) func TestExchangeCredentials(t *testing.T) { t.Parallel() assert.ErrorIs(t, testExchangeCredentials(t), gss.ErrNotSupported) } func TestExchangeKeytab(t *testing.T) { t.Parallel() assert.ErrorIs(t, testExchangeKeytab(t), gss.ErrNotSupported) } func TestNewClientWithConfig(t *testing.T) { t.Parallel() _, err := gss.NewClient(new(dns.Client), gss.WithConfig("")) assert.NotNil(t, err) } golang-github-bodgit-tsig-1.3.1/gss/client.go000066400000000000000000000044371520706045100210660ustar00rootroot00000000000000package gss import ( "encoding/hex" "github.com/bodgit/tsig" "github.com/go-logr/logr" multierror "github.com/hashicorp/go-multierror" "github.com/miekg/dns" ) var _ dns.TsigProvider = new(Client) // Generate generates the TSIG MAC based on the established context. // It is called with the bytes of the DNS message, and the partial TSIG // record containing the algorithm and name which is the negotiated TKEY // for this context. // It returns the bytes for the TSIG MAC and any error that occurred. func (c *Client) Generate(msg []byte, t *dns.TSIG) ([]byte, error) { if dns.CanonicalName(t.Algorithm) != tsig.GSS { return nil, dns.ErrKeyAlg } c.m.RLock() defer c.m.RUnlock() ctx, ok := c.ctx[t.Hdr.Name] if !ok { return nil, dns.ErrSecret } return c.generate(ctx, msg) } // Verify verifies the TSIG MAC based on the established context. // It is called with the bytes of the DNS message, and the TSIG record // containing the algorithm, MAC, and name which is the negotiated TKEY // for this context. // It returns any error that occurred. func (c *Client) Verify(stripped []byte, t *dns.TSIG) error { if dns.CanonicalName(t.Algorithm) != tsig.GSS { return dns.ErrKeyAlg } c.m.RLock() defer c.m.RUnlock() ctx, ok := c.ctx[t.Hdr.Name] if !ok { return dns.ErrSecret } mac, err := hex.DecodeString(t.MAC) if err != nil { return err } return c.verify(ctx, stripped, mac) } func (c *Client) close() error { c.m.RLock() keys := make([]string, 0, len(c.ctx)) for k := range c.ctx { keys = append(keys, k) } c.m.RUnlock() var errs error for _, k := range keys { errs = multierror.Append(errs, c.DeleteContext(k)) } return errs } func (c *Client) setOption(options ...func(*Client) error) error { for _, option := range options { if err := option(c); err != nil { return err } } return nil } // SetConfig sets the Kerberos configuration used by c. func (c *Client) SetConfig(config string) error { return c.setOption(WithConfig(config)) } // WithLogger sets the logger used. func WithLogger(logger logr.Logger) func(*Client) error { return func(c *Client) error { c.logger = logger.WithName("client") return nil } } // SetLogger sets the logger used by c. func (c *Client) SetLogger(logger logr.Logger) error { return c.setOption(WithLogger(logger)) } golang-github-bodgit-tsig-1.3.1/gss/export_test.go000066400000000000000000000000631520706045100221570ustar00rootroot00000000000000package gss var ErrNotSupported = errNotSupported golang-github-bodgit-tsig-1.3.1/gss/gokrb5.go000066400000000000000000000116371520706045100210010ustar00rootroot00000000000000//go:build !windows && !apcera // +build !windows,!apcera package gss import ( "encoding/hex" "net" "sync" "time" wrapper "github.com/bodgit/gssapi" "github.com/bodgit/tsig" "github.com/bodgit/tsig/internal/util" "github.com/go-logr/logr" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/miekg/dns" ) // Client maps the TKEY name to the context that negotiated it as // well as any other internal state. type Client struct { m sync.RWMutex client *dns.Client config string ctx map[string]*wrapper.Initiator logger logr.Logger } // WithConfig sets the Kerberos configuration used. func WithConfig(config string) func(*Client) error { return func(c *Client) error { c.config = config return nil } } // NewClient performs any library initialization necessary. // It returns a context handle for any further functions along with any error // that occurred. func NewClient(dnsClient *dns.Client, options ...func(*Client) error) (*Client, error) { client, err := util.CopyDNSClient(dnsClient) if err != nil { return nil, err } client.TsigProvider = new(gssNoVerify) c := &Client{ client: client, ctx: make(map[string]*wrapper.Initiator), logger: logr.Discard(), } if err := c.setOption(options...); err != nil { return nil, err } return c, nil } // Close deletes any active contexts and unloads any underlying libraries as // necessary. // It returns any error that occurred. func (c *Client) Close() error { return c.close() } func (c *Client) generate(ctx *wrapper.Initiator, msg []byte) ([]byte, error) { return ctx.MakeSignature(msg) } func (c *Client) verify(ctx *wrapper.Initiator, stripped, mac []byte) error { return ctx.VerifySignature(stripped, mac) } func (c *Client) negotiateContext(host string, options []wrapper.Option[wrapper.Initiator]) (string, time.Time, error) { options = append(options, wrapper.WithConfig(c.config), wrapper.WithLogger[wrapper.Initiator](c.logger)) ctx, err := wrapper.NewInitiator(options...) if err != nil { return "", time.Time{}, err } hostname, _, err := net.SplitHostPort(host) if err != nil { return "", time.Time{}, err } keyname, err := generateTKEYName(hostname) if err != nil { return "", time.Time{}, err } spn := generateSPN(hostname) flags := gssapi.ContextFlagMutual | gssapi.ContextFlagReplay | gssapi.ContextFlagInteg output, cont, err := ctx.Initiate(spn, flags, nil) if err != nil { return "", time.Time{}, err } var tkey *dns.TKEY for cont { // We don't care about non-TKEY answers, no additional RR's to send, and no signing tkey, _, err = util.ExchangeTKEY(c.client, host, keyname, tsig.GSS, util.TkeyModeGSS, 3600, output, nil, "", "") if err != nil { return "", time.Time{}, err } if tkey.Header().Name != keyname { return "", time.Time{}, errDoesNotMatch } var input []byte if input, err = hex.DecodeString(tkey.Key); err != nil { return "", time.Time{}, err } output, cont, err = ctx.Initiate(spn, flags, input) if err != nil { return "", time.Time{}, err } } c.m.Lock() defer c.m.Unlock() c.ctx[keyname] = ctx return keyname, ctx.Expiry(), nil } // NegotiateContext exchanges RFC 2930 TKEY records with the indicated DNS // server to establish a security context using the current user. // It returns the negotiated TKEY name, expiration time, and any error that // occurred. func (c *Client) NegotiateContext(host string) (string, time.Time, error) { return c.negotiateContext(host, nil) } // NegotiateContextWithCredentials exchanges RFC 2930 TKEY records with the // indicated DNS server to establish a security context using the provided // credentials. // It returns the negotiated TKEY name, expiration time, and any error that // occurred. func (c *Client) NegotiateContextWithCredentials(host, domain, username, password string) (string, time.Time, error) { options := []wrapper.Option[wrapper.Initiator]{ wrapper.WithDomain(domain), wrapper.WithUsername(username), wrapper.WithPassword(password), } return c.negotiateContext(host, options) } // NegotiateContextWithKeytab exchanges RFC 2930 TKEY records with the // indicated DNS server to establish a security context using the provided // keytab. // It returns the negotiated TKEY name, expiration time, and any error that // occurred. func (c *Client) NegotiateContextWithKeytab(host, domain, username, path string) (string, time.Time, error) { options := []wrapper.Option[wrapper.Initiator]{ wrapper.WithDomain(domain), wrapper.WithUsername(username), wrapper.WithKeytab[wrapper.Initiator](path), } return c.negotiateContext(host, options) } // DeleteContext deletes the active security context associated with the given // TKEY name. // It returns any error that occurred. func (c *Client) DeleteContext(keyname string) error { c.m.Lock() defer c.m.Unlock() ctx, ok := c.ctx[keyname] if !ok { return errNoSuchContext } if err := ctx.Close(); err != nil { return err } delete(c.ctx, keyname) return nil } golang-github-bodgit-tsig-1.3.1/gss/gokrb5_test.go000066400000000000000000000010331520706045100220250ustar00rootroot00000000000000//go:build !windows && !apcera // +build !windows,!apcera package gss_test import ( "testing" "github.com/bodgit/tsig/gss" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) func TestExchangeCredentials(t *testing.T) { t.Parallel() assert.Nil(t, testExchangeCredentials(t)) } func TestExchangeKeytab(t *testing.T) { t.Parallel() assert.Nil(t, testExchangeKeytab(t)) } func TestNewClientWithConfig(t *testing.T) { t.Parallel() _, err := gss.NewClient(new(dns.Client), gss.WithConfig("")) assert.Nil(t, err) } golang-github-bodgit-tsig-1.3.1/gss/gss.go000066400000000000000000000056031520706045100204000ustar00rootroot00000000000000/* Package gss implements RFC 3645 GSS-TSIG functions. This permits sending signed dynamic DNS update messages to Windows servers that have the zone require "Secure only" updates. Example client: import ( "fmt" "time" "github.com/bodgit/tsig" "github.com/bodgit/tsig/gss" "github.com/miekg/dns" ) func main() { dnsClient := new(dns.Client) dnsClient.Net = "tcp" gssClient, err := gss.NewClient(dnsClient) if err != nil { panic(err) } defer gssClient.Close() host := "ns.example.com:53" // Negotiate a context with the chosen server using the // current user. See also // gssClient.NegotiateContextWithCredentials() and // gssClient.NegotiateContextWithKeytab() for alternatives keyname, _, err := gssClient.NegotiateContext(host) if err != nil { panic(err) } dnsClient.TsigProvider = gssClient // Use the DNS client as normal msg := new(dns.Msg) msg.SetUpdate(dns.Fqdn("example.com")) insert, err := dns.NewRR("test.example.com. 300 A 192.0.2.1") if err != nil { panic(err) } msg.Insert([]dns.RR{insert}) msg.SetTsig(keyname, tsig.GSS, 300, time.Now().Unix()) rr, _, err := dnsClient.Exchange(msg, host) if err != nil { panic(err) } if rr.Rcode != dns.RcodeSuccess { fmt.Printf("DNS error: %s (%d)\n", dns.RcodeToString[rr.Rcode], rr.Rcode) } // Cleanup the context err = gssClient.DeleteContext(keyname) if err != nil { panic(err) } } Under the hood, GSSAPI is used on platforms other than Windows whilst Windows uses native SSPI which has a similar API. */ package gss import ( "crypto/rand" "errors" "fmt" "math/big" "github.com/bodgit/tsig" "github.com/miekg/dns" ) var ( errNotSupported = errors.New("not supported") //nolint:nolintlint,unused errDoesNotMatch = errors.New("TKEY name does not match") errNoSuchContext = errors.New("no such context") ) // gssNoVerify is a dns.TsigProvider that skips any GSS-TSIG verification. // // BIND doesn't sign TKEY responses but Windows does, using the key you're // currently negotiating so it creates a chicken & egg problem. According // to the RFC, verification isn't needed as the TKEY response should be // cryptographically secure anyway. type gssNoVerify struct{} func (*gssNoVerify) Generate(_ []byte, t *dns.TSIG) ([]byte, error) { if dns.CanonicalName(t.Algorithm) != tsig.GSS { return nil, dns.ErrKeyAlg } return nil, dns.ErrSecret } func (*gssNoVerify) Verify(_ []byte, t *dns.TSIG) error { if dns.CanonicalName(t.Algorithm) != tsig.GSS { return dns.ErrKeyAlg } return nil } func generateTKEYName(host string) (string, error) { i, err := rand.Int(rand.Reader, big.NewInt(0x7fffffff)) if err != nil { return "", err } return dns.Fqdn(fmt.Sprintf("%d.sig-%s", i.Int64(), host)), nil } func generateSPN(host string) string { if dns.IsFqdn(host) { return fmt.Sprintf("DNS/%s", host[:len(host)-1]) } return fmt.Sprintf("DNS/%s", host) } golang-github-bodgit-tsig-1.3.1/gss/gss_internal_test.go000066400000000000000000000010121520706045100233210ustar00rootroot00000000000000package gss import ( "regexp" "testing" "github.com/stretchr/testify/assert" ) func TestGenerateTKEYName(t *testing.T) { t.Parallel() tkey, err := generateTKEYName("host.example.com") assert.Nil(t, err) assert.Regexp(t, regexp.MustCompile(`^\d+\.sig-host\.example\.com\.$`), tkey) } func TestGenerateSPN(t *testing.T) { t.Parallel() spn := generateSPN("host.example.com") assert.Equal(t, "DNS/host.example.com", spn) spn = generateSPN("host.example.com.") assert.Equal(t, "DNS/host.example.com", spn) } golang-github-bodgit-tsig-1.3.1/gss/gss_test.go000066400000000000000000000076311520706045100214420ustar00rootroot00000000000000package gss_test import ( "fmt" "net" "os" "runtime" "testing" "time" "github.com/bodgit/tsig" "github.com/bodgit/tsig/gss" "github.com/go-logr/logr" "github.com/go-logr/logr/testr" multierror "github.com/hashicorp/go-multierror" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) const dnsClientTransport = "tcp" func testEnvironmentVariables(t *testing.T) (string, string, string, string, string, string) { t.Helper() var ( host string port = "53" realm string username string password string keytab string errs *multierror.Error ) for _, env := range []struct { ptr *string name string optional bool }{ { &host, "DNS_HOST", false, }, { &port, "DNS_PORT", true, }, { &realm, "DNS_REALM", false, }, { &username, "DNS_USERNAME", false, }, { &password, "DNS_PASSWORD", false, }, { &keytab, "DNS_KEYTAB", runtime.GOOS == "windows", }, } { if v, ok := os.LookupEnv(env.name); ok { *env.ptr = v } else if !env.optional { errs = multierror.Append(errs, fmt.Errorf("%s is not set", env.name)) } } if errs.ErrorOrNil() != nil { t.Fatal(errs) } return host, port, realm, username, password, keytab } func testExchange(t *testing.T) (err error) { t.Helper() if testing.Short() { t.Skip("skipping integration test") } //nolint:dogsled host, port, _, _, _, _ := testEnvironmentVariables(t) dnsClient := new(dns.Client) dnsClient.Net = dnsClientTransport gssClient, err := gss.NewClient(dnsClient, gss.WithLogger(testr.New(t))) if err != nil { return err } defer func() { err = multierror.Append(err, gssClient.Close()).ErrorOrNil() }() keyname, _, err := gssClient.NegotiateContext(net.JoinHostPort(host, port)) if err != nil { return err } dnsClient.TsigProvider = gssClient msg := new(dns.Msg) msg.SetUpdate(dns.Fqdn("example.com")) insert, err := dns.NewRR("test.example.com. 300 A 192.0.2.1") if err != nil { return err } msg.Insert([]dns.RR{insert}) msg.SetTsig(keyname, tsig.GSS, 300, time.Now().Unix()) rr, _, err := dnsClient.Exchange(msg, net.JoinHostPort(host, port)) if err != nil { return err } if rr.Rcode != dns.RcodeSuccess { return fmt.Errorf("DNS error: %s (%d)", dns.RcodeToString[rr.Rcode], rr.Rcode) } return gssClient.DeleteContext(keyname) } func testExchangeCredentials(t *testing.T) (err error) { t.Helper() if testing.Short() { t.Skip("skipping integration test") } host, port, realm, username, password, _ := testEnvironmentVariables(t) dnsClient := new(dns.Client) dnsClient.Net = dnsClientTransport gssClient, err := gss.NewClient(dnsClient) if err != nil { return err } defer func() { err = multierror.Append(err, gssClient.Close()).ErrorOrNil() }() if err = gssClient.SetLogger(testr.New(t)); err != nil { return err } keyname, _, err := gssClient.NegotiateContextWithCredentials(net.JoinHostPort(host, port), realm, username, password) if err != nil { return err } return gssClient.DeleteContext(keyname) } func testExchangeKeytab(t *testing.T) (err error) { t.Helper() if testing.Short() { t.Skip("skipping integration test") } host, port, realm, username, _, keytab := testEnvironmentVariables(t) dnsClient := new(dns.Client) dnsClient.Net = dnsClientTransport gssClient, err := gss.NewClient(dnsClient, gss.WithLogger(testr.New(t))) if err != nil { return err } defer func() { err = multierror.Append(err, gssClient.Close()).ErrorOrNil() }() keyname, _, err := gssClient.NegotiateContextWithKeytab(net.JoinHostPort(host, port), realm, username, keytab) if err != nil { return err } return gssClient.DeleteContext(keyname) } func TestExchange(t *testing.T) { t.Parallel() assert.Nil(t, testExchange(t)) } func TestNewClientWithLogger(t *testing.T) { t.Parallel() _, err := gss.NewClient(new(dns.Client), gss.WithLogger(logr.Discard())) assert.Nil(t, err) } golang-github-bodgit-tsig-1.3.1/gss/sspi.go000066400000000000000000000115461520706045100205650ustar00rootroot00000000000000//go:build windows // +build windows package gss import ( "encoding/hex" "net" "sync" "time" "github.com/alexbrainman/sspi" "github.com/alexbrainman/sspi/negotiate" "github.com/bodgit/tsig" "github.com/bodgit/tsig/internal/util" "github.com/go-logr/logr" multierror "github.com/hashicorp/go-multierror" "github.com/miekg/dns" ) // Client maps the TKEY name to the context that negotiated it as // well as any other internal state. type Client struct { m sync.RWMutex client *dns.Client ctx map[string]*negotiate.ClientContext logger logr.Logger } // WithConfig sets the Kerberos configuration used. func WithConfig(_ string) func(*Client) error { return func(c *Client) error { return errNotSupported } } // NewClient performs any library initialization necessary. // It returns a context handle for any further functions along with any error // that occurred. func NewClient(dnsClient *dns.Client, options ...func(*Client) error) (*Client, error) { client, err := util.CopyDNSClient(dnsClient) if err != nil { return nil, err } client.TsigProvider = new(gssNoVerify) c := &Client{ client: client, ctx: make(map[string]*negotiate.ClientContext), logger: logr.Discard(), } if err := c.setOption(options...); err != nil { return nil, err } return c, nil } // Close deletes any active contexts and unloads any underlying libraries as // necessary. // It returns any error that occurred. func (c *Client) Close() error { return c.close() } func (c *Client) generate(ctx *negotiate.ClientContext, msg []byte) ([]byte, error) { return ctx.MakeSignature(msg, 0, 0) } func (c *Client) verify(ctx *negotiate.ClientContext, stripped, mac []byte) error { _, err := ctx.VerifySignature(stripped, mac, 0) return err } func (c *Client) negotiateContext(host string, creds *sspi.Credentials) (string, time.Time, error) { hostname, _, err := net.SplitHostPort(host) if err != nil { return "", time.Time{}, err } keyname, err := generateTKEYName(hostname) if err != nil { return "", time.Time{}, err } ctx, output, err := negotiate.NewClientContext(creds, generateSPN(hostname)) if err != nil { return "", time.Time{}, err } var ( completed bool tkey *dns.TKEY ) for ok := false; !ok; ok = completed { //nolint:lll if tkey, _, err = util.ExchangeTKEY(c.client, host, keyname, tsig.GSS, util.TkeyModeGSS, 3600, output, nil, "", ""); err != nil { return "", time.Time{}, multierror.Append(err, ctx.Release()) } if tkey.Header().Name != keyname { return "", time.Time{}, multierror.Append(errDoesNotMatch, ctx.Release()) } input, err := hex.DecodeString(tkey.Key) if err != nil { return "", time.Time{}, multierror.Append(err, ctx.Release()) } if completed, output, err = ctx.Update(input); err != nil { return "", time.Time{}, multierror.Append(err, ctx.Release()) } } c.m.Lock() defer c.m.Unlock() c.ctx[keyname] = ctx return keyname, ctx.Expiry(), nil } // NegotiateContext exchanges RFC 2930 TKEY records with the indicated DNS // server to establish a security context using the current user. // It returns the negotiated TKEY name, expiration time, and any error that // occurred. func (c *Client) NegotiateContext(host string) (keyname string, expiry time.Time, err error) { creds, err := negotiate.AcquireCurrentUserCredentials() if err != nil { return "", time.Time{}, err } defer func() { err = multierror.Append(err, creds.Release()).ErrorOrNil() }() return c.negotiateContext(host, creds) } // NegotiateContextWithCredentials exchanges RFC 2930 TKEY records with the // indicated DNS server to establish a security context using the provided // credentials. // It returns the negotiated TKEY name, expiration time, and any error that // occurred. // //nolint:lll func (c *Client) NegotiateContextWithCredentials(host, domain, username, password string) (keyname string, expiry time.Time, err error) { creds, err := negotiate.AcquireUserCredentials(domain, username, password) if err != nil { return "", time.Time{}, err } defer func() { err = multierror.Append(err, creds.Release()).ErrorOrNil() }() return c.negotiateContext(host, creds) } // NegotiateContextWithKeytab exchanges RFC 2930 TKEY records with the // indicated DNS server to establish a security context using the provided // keytab. // It returns the negotiated TKEY name, expiration time, and any error that // occurred. func (c *Client) NegotiateContextWithKeytab(_, _, _, _ string) (string, time.Time, error) { return "", time.Time{}, errNotSupported } // DeleteContext deletes the active security context associated with the given // TKEY name. // It returns any error that occurred. func (c *Client) DeleteContext(keyname string) error { c.m.Lock() defer c.m.Unlock() ctx, ok := c.ctx[keyname] if !ok { return errNoSuchContext } if err := ctx.Release(); err != nil { return err } delete(c.ctx, keyname) return nil } golang-github-bodgit-tsig-1.3.1/gss/sspi_test.go000066400000000000000000000010421520706045100216120ustar00rootroot00000000000000//go:build windows // +build windows package gss_test import ( "testing" "github.com/bodgit/tsig/gss" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) func TestExchangeCredentials(t *testing.T) { t.Parallel() assert.Nil(t, testExchangeCredentials(t)) } func TestExchangeKeytab(t *testing.T) { t.Parallel() assert.ErrorIs(t, testExchangeKeytab(t), gss.ErrNotSupported) } func TestNewClientWithConfig(t *testing.T) { t.Parallel() _, err := gss.NewClient(new(dns.Client), gss.WithConfig("")) assert.NotNil(t, err) } golang-github-bodgit-tsig-1.3.1/hmac.go000066400000000000000000000035331520706045100177200ustar00rootroot00000000000000package tsig import ( "crypto/hmac" "crypto/md5" //nolint:gosec "crypto/sha1" //nolint:gosec "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/hex" "hash" "github.com/miekg/dns" ) // HMAC implements the standard HMAC TSIG methods using the dns.TsigProvider // interface. It holds a map of TSIG key names to base64-encoded secrets. The // key names should be in canonical form, see dns.CanonicalName. type HMAC map[string]string func fromBase64(s []byte) (buf []byte, err error) { buflen := base64.StdEncoding.DecodedLen(len(s)) buf = make([]byte, buflen) n, err := base64.StdEncoding.Decode(buf, s) buf = buf[:n] return } // Generate generates the TSIG MAC using the HMAC algorithm indicated by // t.Algorithm using h[t.Hdr.Name] as the key. // It returns the bytes for the TSIG MAC and any error that occurred. func (h HMAC) Generate(msg []byte, t *dns.TSIG) ([]byte, error) { var f func() hash.Hash switch dns.CanonicalName(t.Algorithm) { case dns.HmacMD5: f = md5.New case dns.HmacSHA1: f = sha1.New case dns.HmacSHA224: f = sha256.New224 case dns.HmacSHA256: f = sha256.New case dns.HmacSHA384: f = sha512.New384 case dns.HmacSHA512: f = sha512.New default: return nil, dns.ErrKeyAlg } secret, ok := h[t.Hdr.Name] if !ok { return nil, dns.ErrSecret } rawsecret, err := fromBase64([]byte(secret)) if err != nil { return nil, err } m := hmac.New(f, rawsecret) m.Write(msg) return m.Sum(nil), nil } // Verify verifies the TSIG MAC using the HMAC algorithm indicated by // t.Algorithm using h[t.Hdr.Name] as the key. // It returns any error that occurred. func (h HMAC) Verify(msg []byte, t *dns.TSIG) error { b, err := h.Generate(msg, t) if err != nil { return err } mac, err := hex.DecodeString(t.MAC) if err != nil { return err } if !hmac.Equal(b, mac) { return dns.ErrSig } return nil } golang-github-bodgit-tsig-1.3.1/hmac_test.go000066400000000000000000000131171520706045100207560ustar00rootroot00000000000000package tsig_test import ( "encoding/base64" "encoding/hex" "testing" "github.com/bodgit/tsig" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) //nolint:funlen func TestHMACGenerate(t *testing.T) { t.Parallel() tables := []struct { name string provider tsig.HMAC msg []byte tsig *dns.TSIG b []byte err error }{ { "md5", tsig.HMAC{"example.": "DRwIYZn6exnhof/mcV/aEQ=="}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacMD5, }, []byte{ 0x0b, 0x78, 0x2f, 0xf6, 0xac, 0xb3, 0xf6, 0xbe, 0x52, 0xdb, 0x22, 0xc7, 0xce, 0x08, 0x11, 0x77, }, nil, }, { "sha1", tsig.HMAC{"example.": "dZFRPtLqbQXGs7SdraTJJSGNSCU="}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacSHA1, }, []byte{ 0xb8, 0xb5, 0xdf, 0xd4, 0x27, 0x85, 0x07, 0x6f, 0x2f, 0x3a, 0xa9, 0xc6, 0xf9, 0xfe, 0x98, 0x68, 0xc5, 0xbd, 0x9b, 0x7a, }, nil, }, { "sha224", tsig.HMAC{"example.": "NaDGqfyc2/Fc0muCPB78CyGPlveTursOxrPVVQ=="}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacSHA224, }, []byte{ 0xfc, 0x1c, 0xf5, 0xd9, 0x5e, 0x1f, 0xb0, 0xd5, 0xad, 0x2d, 0x53, 0x5a, 0x69, 0x2e, 0x47, 0x5c, 0x3a, 0xa8, 0xed, 0x52, 0x41, 0x4c, 0x71, 0x7d, 0xd9, 0x87, 0x3a, 0xcb, }, nil, }, { "sha256", tsig.HMAC{"example.": "BduxMlVUsrEhdgfOLKSLhNE4D3qzDx7dwyRjt7+BDNE="}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacSHA256, }, []byte{ 0xdc, 0x76, 0x07, 0x57, 0xa5, 0x92, 0x01, 0x55, 0x1d, 0x57, 0xdc, 0xaf, 0x43, 0x6a, 0x45, 0xdc, 0xec, 0xa9, 0xb7, 0x1b, 0x63, 0x37, 0x63, 0x90, 0x4b, 0x63, 0x5d, 0xc3, 0x96, 0xeb, 0x42, 0xd6, }, nil, }, { "sha384", tsig.HMAC{"example.": "xqbc2K8kfLDw3yNOOw9kloxrLPX0ILoGK4sxZwVOgDnGzcp9DZu5nDQMZBofAIYf"}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacSHA384, }, []byte{ 0x21, 0x29, 0xfa, 0x1c, 0x10, 0x4b, 0x12, 0x81, 0x95, 0x98, 0x36, 0x5a, 0x92, 0x88, 0x1e, 0x5a, 0x26, 0x76, 0x28, 0x5a, 0x0c, 0xe7, 0x53, 0xa5, 0x3c, 0xb6, 0xad, 0x12, 0xc2, 0x7b, 0xb9, 0xd5, 0x88, 0x2f, 0x24, 0xae, 0x39, 0x54, 0xd5, 0xbb, 0x95, 0x7f, 0x30, 0x1c, 0x42, 0x61, 0x22, 0xc5, }, nil, }, { "sha512", tsig.HMAC{"example.": "WCltYAUyQQjslkIIOXnvJkC3bSlCPEsl6gYEzkIyUbnXbmJZA5PTgSL8fLlwfDKYJl/SiFMTOzQxWvH7AmUvSw=="}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacSHA512, }, []byte{ 0xdb, 0x3e, 0x97, 0x64, 0x17, 0x8a, 0x93, 0x60, 0x19, 0x6b, 0x80, 0xe4, 0xac, 0xba, 0xbd, 0xb7, 0x1e, 0xe9, 0xb4, 0xf6, 0xc3, 0x0e, 0xc0, 0x2c, 0xcd, 0xcf, 0xf3, 0xff, 0x29, 0x8c, 0x03, 0xfa, 0x4b, 0x58, 0xf0, 0xfe, 0xaa, 0x15, 0x6e, 0x77, 0x8f, 0x98, 0x65, 0x72, 0x3c, 0x94, 0x4e, 0x3f, 0xc9, 0xdc, 0x4c, 0x88, 0x7c, 0x4d, 0xfb, 0x23, 0x8a, 0xad, 0xe5, 0x4f, 0xcc, 0x73, 0x50, 0x59, }, nil, }, { "algorithm", tsig.HMAC{"example.": ""}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: tsig.GSS, }, nil, dns.ErrKeyAlg, }, { "secret", tsig.HMAC{}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacMD5, }, nil, dns.ErrSecret, }, { "garbage", tsig.HMAC{"example.": "garbage"}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacMD5, }, nil, base64.CorruptInputError(4), }, } for _, table := range tables { table := table t.Run(table.name, func(t *testing.T) { t.Parallel() b, err := table.provider.Generate(table.msg, table.tsig) assert.Equal(t, table.b, b) assert.Equal(t, table.err, err) }) } } //nolint:funlen func TestHMACVerify(t *testing.T) { t.Parallel() tables := []struct { name string provider tsig.HMAC msg []byte tsig *dns.TSIG err error }{ { "md5", tsig.HMAC{"example.": "DRwIYZn6exnhof/mcV/aEQ=="}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacMD5, MAC: hex.EncodeToString([]byte{ 0x0b, 0x78, 0x2f, 0xf6, 0xac, 0xb3, 0xf6, 0xbe, 0x52, 0xdb, 0x22, 0xc7, 0xce, 0x08, 0x11, 0x77, }), }, nil, }, { "algorithm", tsig.HMAC{"example.": "DRwIYZn6exnhof/mcV/aEQ=="}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: tsig.GSS, MAC: "", }, dns.ErrKeyAlg, }, { "garbage", tsig.HMAC{"example.": "DRwIYZn6exnhof/mcV/aEQ=="}, []byte("message"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacMD5, MAC: "garbage", }, hex.InvalidByteError(0x67), }, { "signature", tsig.HMAC{"example.": "DRwIYZn6exnhof/mcV/aEQ=="}, []byte("different"), &dns.TSIG{ Hdr: dns.RR_Header{ Name: "example.", }, Algorithm: dns.HmacMD5, MAC: hex.EncodeToString([]byte{ 0x0b, 0x78, 0x2f, 0xf6, 0xac, 0xb3, 0xf6, 0xbe, 0x52, 0xdb, 0x22, 0xc7, 0xce, 0x08, 0x11, 0x77, }), }, dns.ErrSig, }, } for _, table := range tables { table := table t.Run(table.name, func(t *testing.T) { t.Parallel() err := table.provider.Verify(table.msg, table.tsig) assert.Equal(t, table.err, err) }) } } golang-github-bodgit-tsig-1.3.1/internal/000077500000000000000000000000001520706045100202715ustar00rootroot00000000000000golang-github-bodgit-tsig-1.3.1/internal/util/000077500000000000000000000000001520706045100212465ustar00rootroot00000000000000golang-github-bodgit-tsig-1.3.1/internal/util/util.go000066400000000000000000000100471520706045100225540ustar00rootroot00000000000000/* Package util contains utility routines. */ package util import ( "encoding/hex" "errors" "fmt" "time" "github.com/bodgit/tsig" "github.com/miekg/dns" ) const ( _ uint16 = iota // Reserved, RFC 2930, section 2.5 // TkeyModeServer is used for server assigned keying. TkeyModeServer // TkeyModeDH is used for Diffie-Hellman exchanged keying. TkeyModeDH // TkeyModeGSS is used for GSS-API establishment. TkeyModeGSS // TkeyModeResolver is used for resolver assigned keying. TkeyModeResolver // TkeyModeDelete is used for key deletion. TkeyModeDelete ) // Exchanger is the interface a DNS client is expected to implement. type Exchanger interface { Exchange(*dns.Msg, string) (*dns.Msg, time.Duration, error) } // CopyDNSClient performs a deep copy of dnsClient, changing the network to // TCP. If the existing network is configured to only use IPv4 or IPv6 then // the appropriate network is chosen to maintain this choice. func CopyDNSClient(dnsClient *dns.Client) (*dns.Client, error) { client := &dns.Client{} *client = *dnsClient switch client.Net { case "tcp", "tcp4", "tcp6": break case "", "udp": client.Net = "tcp" case "ip4", "udp4": client.Net = "tcp4" case "ip6", "udp6": client.Net = "tcp6" default: return nil, fmt.Errorf("unsupported transport '%s'", client.Net) } if client.TsigSecret == nil { client.TsigSecret = make(map[string]string) } return client, nil } func calculateTimes(mode uint16, lifetime uint32) (uint32, uint32, error) { switch mode { case TkeyModeDH: fallthrough case TkeyModeGSS: now := time.Now().Unix() return uint32(now), uint32(now) + lifetime, nil case TkeyModeDelete: return 0, 0, nil default: return 0, 0, fmt.Errorf("unsupported TKEY mode %d", mode) } } // ExchangeTKEY exchanges TKEY records with the given host using the given // key name, algorithm, mode, and lifetime with the provided input payload. // Any additional DNS records are also sent and the exchange can be secured // with TSIG if a key name, algorithm and MAC are provided. // The TKEY record is returned along with any other DNS records in the // response along with any error that occurred. // //nolint:cyclop,funlen func ExchangeTKEY(client Exchanger, host, keyname, algorithm string, mode uint16, lifetime uint32, input []byte, extra []dns.RR, tsigname, tsigalgo string) (*dns.TKEY, []dns.RR, error) { //nolint:lll msg := &dns.Msg{ MsgHdr: dns.MsgHdr{ Id: dns.Id(), RecursionDesired: false, }, Question: make([]dns.Question, 1), Extra: make([]dns.RR, 1+len(extra)), } msg.Question[0] = dns.Question{ Name: keyname, Qtype: dns.TypeTKEY, Qclass: dns.ClassANY, } inception, expiration, err := calculateTimes(mode, lifetime) if err != nil { return nil, nil, err } msg.Extra[0] = &dns.TKEY{ Hdr: dns.RR_Header{ Name: keyname, Rrtype: dns.TypeTKEY, Class: dns.ClassANY, Ttl: 0, }, Algorithm: algorithm, Mode: mode, Inception: inception, Expiration: expiration, KeySize: uint16(len(input)), Key: hex.EncodeToString(input), } msg.Extra = append(msg.Extra, extra...) if dns.CanonicalName(algorithm) != tsig.GSS && tsigname != "" && tsigalgo != "" { msg.SetTsig(tsigname, tsigalgo, 300, time.Now().Unix()) } rr, _, err := client.Exchange(msg, host) if err != nil { return nil, nil, err } if rr.Rcode != dns.RcodeSuccess { return nil, nil, fmt.Errorf("DNS error: %s (%d)", dns.RcodeToString[rr.Rcode], rr.Rcode) } additional := []dns.RR{} var tkey *dns.TKEY for _, ans := range rr.Answer { switch t := ans.(type) { case *dns.TKEY: // There mustn't be more than one TKEY answer RR if tkey != nil { return nil, nil, errors.New("multiple TKEY responses") } tkey = t default: additional = append(additional, ans) } } // There should always be at least a TKEY answer RR if tkey == nil { return nil, nil, errors.New("received no TKEY response") } if tkey.Error != 0 { return nil, nil, fmt.Errorf("TKEY error: %s (%d)", dns.RcodeToString[int(tkey.Error)], tkey.Error) } return tkey, additional, nil } golang-github-bodgit-tsig-1.3.1/internal/util/util_internal_test.go000066400000000000000000000011221520706045100255010ustar00rootroot00000000000000package util import ( "testing" "github.com/stretchr/testify/assert" ) func TestCalculateTimes(t *testing.T) { t.Parallel() lifetime := uint32(3600) t0, t1, err := calculateTimes(TkeyModeDH, lifetime) assert.Nil(t, err) assert.Equal(t, lifetime, t1-t0) t0, t1, err = calculateTimes(TkeyModeGSS, lifetime) assert.Nil(t, err) assert.Equal(t, lifetime, t1-t0) t0, t1, err = calculateTimes(TkeyModeDelete, lifetime) assert.Nil(t, err) assert.Equal(t, uint32(0), t0) assert.Equal(t, uint32(0), t1) _, _, err = calculateTimes(TkeyModeServer, lifetime) assert.NotNil(t, err) } golang-github-bodgit-tsig-1.3.1/internal/util/util_test.go000066400000000000000000000077151520706045100236230ustar00rootroot00000000000000package util_test import ( "errors" "testing" "time" "github.com/bodgit/tsig" "github.com/bodgit/tsig/internal/util" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type FakeClient struct { Msg *dns.Msg Duration time.Duration Err error } func (c *FakeClient) Exchange(_ *dns.Msg, _ string) (*dns.Msg, time.Duration, error) { if c.Err != nil { return nil, 0, c.Err } return c.Msg, c.Duration, nil } //nolint:funlen func TestExchangeTKEY(t *testing.T) { t.Parallel() now := uint32(time.Now().Unix()) goodTKEY := &dns.TKEY{ Hdr: dns.RR_Header{ Name: "test.example.com.", Rrtype: dns.TypeTKEY, Class: dns.ClassANY, Ttl: 0, }, Algorithm: tsig.GSS, Mode: util.TkeyModeGSS, Inception: now, Expiration: now + 3600, KeySize: 4, Key: "deadbeef", } tables := []struct { name string client FakeClient host string keyname string algorithm string mode uint16 lifetime uint32 input []byte extra []dns.RR tsigname string tsigalgo string expectedTKEY *dns.TKEY expectedAdditional []dns.RR expectedErr error }{ { name: "ok", client: FakeClient{ Msg: &dns.Msg{ Answer: []dns.RR{ goodTKEY, }, }, Duration: 0, Err: nil, }, host: "ns.example.com.", keyname: "test.example.com.", algorithm: tsig.GSS, mode: util.TkeyModeGSS, lifetime: 3600, expectedTKEY: goodTKEY, expectedAdditional: []dns.RR{}, expectedErr: nil, }, } for _, table := range tables { table := table t.Run(table.name, func(t *testing.T) { t.Parallel() //nolint:lll tkey, additional, err := util.ExchangeTKEY(&table.client, table.host, table.keyname, table.algorithm, table.mode, table.lifetime, table.input, table.extra, table.tsigname, table.tsigalgo) assert.Equal(t, table.expectedTKEY, tkey) assert.Equal(t, table.expectedAdditional, additional) assert.Equal(t, table.expectedErr, err) }) } } //nolint:funlen func TestCopyDNSClient(t *testing.T) { t.Parallel() tables := []struct { name string client dns.Client net string err error }{ { "tcp", dns.Client{ Net: "tcp", }, "tcp", nil, }, { "udp", dns.Client{ Net: "udp", }, "tcp", nil, }, { "udp4", dns.Client{ Net: "udp4", }, "tcp4", nil, }, { "udp6", dns.Client{ Net: "udp6", }, "tcp6", nil, }, { "invalid", dns.Client{ Net: "invalid", }, "tcp6", errors.New("unsupported transport 'invalid'"), }, } for _, table := range tables { table := table t.Run(table.name, func(t *testing.T) { t.Parallel() client, err := util.CopyDNSClient(&table.client) if table.err == nil { assert.Equal(t, table.net, client.Net) } assert.Equal(t, table.err, err) }) } } type mockTsigProvider struct { Name string } func (f mockTsigProvider) Generate(_ []byte, _ *dns.TSIG) ([]byte, error) { return nil, nil } func (f mockTsigProvider) Verify(_ []byte, _ *dns.TSIG) error { return nil } func TestCopyDNSClient_shallow_copy(t *testing.T) { t.Parallel() dnsClient := &dns.Client{ Net: "udp", TsigProvider: &mockTsigProvider{Name: "original"}, } client, err := util.CopyDNSClient(dnsClient) require.NoError(t, err) client.TsigProvider = &mockTsigProvider{Name: "copy"} originalProvider, ok := dnsClient.TsigProvider.(*mockTsigProvider) require.True(t, ok) assert.Equal(t, "original", originalProvider.Name) assert.Equal(t, "udp", dnsClient.Net) assert.Nil(t, dnsClient.TsigSecret) copyProvider, ok := client.TsigProvider.(*mockTsigProvider) require.True(t, ok) assert.Equal(t, "copy", copyProvider.Name) assert.Equal(t, "tcp", client.Net) assert.NotNil(t, client.TsigSecret) } golang-github-bodgit-tsig-1.3.1/multi.go000066400000000000000000000024041520706045100201360ustar00rootroot00000000000000package tsig import ( "errors" "github.com/miekg/dns" ) type multiProvider struct { providers []dns.TsigProvider } func (mp *multiProvider) Generate(msg []byte, t *dns.TSIG) (b []byte, err error) { for _, p := range mp.providers { if b, err = p.Generate(msg, t); err == nil || !errors.Is(err, dns.ErrKeyAlg) { return } } return nil, dns.ErrKeyAlg } func (mp *multiProvider) Verify(msg []byte, t *dns.TSIG) (err error) { for _, p := range mp.providers { if err = p.Verify(msg, t); err == nil || !errors.Is(err, dns.ErrKeyAlg) { return } } return dns.ErrKeyAlg } // MultiProvider creates a dns.TsigProvider that chains the provided input // providers. This allows multiple TSIG algorithms. // // Each provider is called in turn and if it returns dns.ErrKeyAlg the next // provider in the list is tried. On success or any other error, the result is // returned; it does not continue down the list. func MultiProvider(providers ...dns.TsigProvider) dns.TsigProvider { allProviders := make([]dns.TsigProvider, 0, len(providers)) for _, p := range providers { if mp, ok := p.(*multiProvider); ok { allProviders = append(allProviders, mp.providers...) } else { allProviders = append(allProviders, p) } } return &multiProvider{allProviders} } golang-github-bodgit-tsig-1.3.1/multi_test.go000066400000000000000000000050371520706045100212020ustar00rootroot00000000000000package tsig_test import ( "errors" "testing" "github.com/bodgit/tsig" "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) var ( errProvider = errors.New("provider error") testSignature = []byte("a good signature") //nolint:gochecknoglobals ) type unsupportedProvider struct{} func (unsupportedProvider) Generate(_ []byte, _ *dns.TSIG) ([]byte, error) { return nil, dns.ErrKeyAlg } func (unsupportedProvider) Verify(_ []byte, _ *dns.TSIG) error { return dns.ErrKeyAlg } type errorProvider struct{} func (errorProvider) Generate(_ []byte, _ *dns.TSIG) ([]byte, error) { return nil, errProvider } func (errorProvider) Verify(_ []byte, _ *dns.TSIG) error { return errProvider } type testProvider struct{} func (testProvider) Generate(_ []byte, _ *dns.TSIG) ([]byte, error) { return testSignature, nil } func (testProvider) Verify(_ []byte, _ *dns.TSIG) error { return nil } func TestMultiProviderGenerate(t *testing.T) { t.Parallel() tables := []struct { name string provider dns.TsigProvider signature []byte err error }{ { "good", tsig.MultiProvider(new(testProvider)), testSignature, nil, }, { "unsupported good", tsig.MultiProvider(new(unsupportedProvider), new(testProvider)), testSignature, nil, }, { "error good", tsig.MultiProvider(new(errorProvider), new(testProvider)), nil, errProvider, }, { "all unsupported", tsig.MultiProvider(new(unsupportedProvider)), nil, dns.ErrKeyAlg, }, { "nested", tsig.MultiProvider(tsig.MultiProvider(new(testProvider))), testSignature, nil, }, } for _, table := range tables { table := table t.Run(table.name, func(t *testing.T) { t.Parallel() b, err := table.provider.Generate(nil, nil) assert.Equal(t, table.signature, b) assert.Equal(t, table.err, err) }) } } func TestMultiProviderVerify(t *testing.T) { t.Parallel() tables := []struct { name string provider dns.TsigProvider err error }{ { "good", tsig.MultiProvider(new(testProvider)), nil, }, { "unsupported good", tsig.MultiProvider(new(unsupportedProvider), new(testProvider)), nil, }, { "error good", tsig.MultiProvider(new(errorProvider), new(testProvider)), errProvider, }, { "all unsuppored", tsig.MultiProvider(new(unsupportedProvider)), dns.ErrKeyAlg, }, } for _, table := range tables { table := table t.Run(table.name, func(t *testing.T) { t.Parallel() err := table.provider.Verify(nil, nil) assert.Equal(t, table.err, err) }) } } golang-github-bodgit-tsig-1.3.1/release-please-config.json000066400000000000000000000000741520706045100235030ustar00rootroot00000000000000{ "packages": { ".": {} }, "release-type": "go" } golang-github-bodgit-tsig-1.3.1/testdata/000077500000000000000000000000001520706045100202665ustar00rootroot00000000000000golang-github-bodgit-tsig-1.3.1/testdata/Dockerfile000066400000000000000000000032061520706045100222610ustar00rootroot00000000000000FROM rockylinux/rockylinux:9-ubi-init@sha256:836808be6894cc4a9c061e8187a6a2a4cb6ec258a8ff940798be34a2aa6a48eb AS kdc EXPOSE 8088 EXPOSE 8464 RUN yum install -y krb5-workstation && yum update -y && yum clean all COPY --chown=root:root krb5.conf /etc/krb5.conf RUN chmod 644 /etc/krb5.conf RUN yum install -y krb5-server && yum clean all COPY --chown=root:root kdc.conf /var/kerberos/krb5kdc/kdc.conf COPY --chown=root:root kadm5.acl /var/kerberos/krb5kdc/kadm5.acl RUN chmod 600 /var/kerberos/krb5kdc/kdc.conf /var/kerberos/krb5kdc/kadm5.acl RUN systemctl enable krb5kdc.service kadmin.service RUN kdb5_util create -s -r EXAMPLE.COM -P $(echo ${RANDOM}${RANDOM}${RANDOM} | md5sum | cut -d ' ' -f 1) RUN kadmin.local addprinc -pw password test RUN kadmin.local ktadd -norandkey -k /etc/test.keytab test RUN kadmin.local addprinc -randkey DNS/ns.example.com RUN kadmin.local ktadd -k /etc/dns.keytab DNS/ns.example.com FROM rockylinux/rockylinux:9-ubi-init@sha256:836808be6894cc4a9c061e8187a6a2a4cb6ec258a8ff940798be34a2aa6a48eb AS ns EXPOSE 8053 RUN yum install -y krb5-workstation && yum update -y && yum clean all COPY --chown=root:root krb5.conf /etc/krb5.conf RUN chmod 644 /etc/krb5.conf RUN yum install -y bind bind-utils && yum clean all COPY --from=kdc --chown=root:named /etc/dns.keytab /etc/dns.keytab RUN chmod 640 /etc/dns.keytab RUN systemctl enable named.service COPY --chown=root:named named.conf /etc/named.conf RUN chmod 640 /etc/named.conf COPY --chown=named:named db.* /var/named/dynamic/ RUN chmod 644 /var/named/dynamic/db.* FROM scratch AS keytab COPY --from=kdc /etc/test.keytab /test.keytab COPY --from=kdc /etc/dns.keytab /dns.keytab golang-github-bodgit-tsig-1.3.1/testdata/db.10.168.192.in-addr.arpa000066400000000000000000000006531520706045100241070ustar00rootroot00000000000000$ORIGIN . $TTL 259200 ; 3 days 10.168.192.in-addr.arpa IN SOA ns.example.com. hostmaster.example.com. ( 2020122801 ; serial 28800 ; refresh (8 hours) 7200 ; retry (2 hours) 2419200 ; expire (4 weeks) 86400 ; minimum (1 day) ) NS ns.example.com. $ORIGIN 10.168.192.in-addr.arpa. $TTL 3600 ; 1 hour 100 PTR kdc.example.com. 101 PTR ns.example.com. 102 PTR client.example.com. golang-github-bodgit-tsig-1.3.1/testdata/db.example.com000066400000000000000000000006071520706045100230100ustar00rootroot00000000000000$ORIGIN . $TTL 259200 ; 3 days example.com IN SOA ns.example.com. hostmaster.example.com. ( 2020122801 ; serial 28800 ; refresh (8 hours) 7200 ; retry (2 hours) 2419200 ; expire (4 weeks) 86400 ; minimum (1 day) ) NS ns.example.com. $ORIGIN example.com. $TTL 3600 ; 1 hour kdc A 192.168.10.100 ns A 192.168.10.101 client A 192.168.10.102 golang-github-bodgit-tsig-1.3.1/testdata/kadm5.acl000066400000000000000000000000261520706045100217460ustar00rootroot00000000000000*/admin@EXAMPLE.COM * golang-github-bodgit-tsig-1.3.1/testdata/kdc.conf000066400000000000000000000007611520706045100217020ustar00rootroot00000000000000[kdcdefaults] kadmind_port = 8749 kdc_ports = 8088 kdc_tcp_ports = 8088 kpasswd_port = 8464 [realms] EXAMPLE.COM = { #master_key_type = aes256-cts acl_file = /var/kerberos/krb5kdc/kadm5.acl dict_file = /usr/share/dict/words admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab supported_enctypes = aes256-cts:normal aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal camellia256-cts:normal camellia128-cts:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal } golang-github-bodgit-tsig-1.3.1/testdata/krb5.conf000066400000000000000000000010351520706045100217770ustar00rootroot00000000000000[logging] default = FILE:/var/log/krb5libs.log kdc = FILE:/var/log/krb5kdc.log admin_server = FILE:/var/log/kadmind.log [libdefaults] dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h renew_lifetime = 7d forwardable = true rdns = false default_realm = EXAMPLE.COM default_ccache_name = FILE:/tmp/krb5cc_%{uid} default_cc_name = FILE:/tmp/krb5cc_%{uid} [realms] EXAMPLE.COM = { kdc = 127.0.0.1:8088 admin_server = 127.0.0.1:8749 } [domain_realm] .example.com = EXAMPLE.COM example.com = EXAMPLE.COM golang-github-bodgit-tsig-1.3.1/testdata/named.conf000066400000000000000000000042311520706045100222210ustar00rootroot00000000000000// // named.conf // // Provided by Red Hat bind package to configure the ISC BIND named(8) DNS // server as a caching only nameserver (as a localhost DNS resolver only). // // See /usr/share/doc/bind*/sample/ for example named configuration files. // // See the BIND Administrator's Reference Manual (ARM) for details about the // configuration located in /usr/share/doc/bind-{version}/Bv9ARM.html options { listen-on port 8053 { 0.0.0.0/0; }; // listen-on-v6 port 53 { ::1; }; directory "/var/named"; dump-file "/var/named/data/cache_dump.db"; statistics-file "/var/named/data/named_stats.txt"; memstatistics-file "/var/named/data/named_mem_stats.txt"; recursing-file "/var/named/data/named.recursing"; secroots-file "/var/named/data/named.secroots"; // allow-query { localhost; }; /* - If you are building an AUTHORITATIVE DNS server, do NOT enable recursion. - If you are building a RECURSIVE (caching) DNS server, you need to enable recursion. - If your recursive DNS server has a public IP address, you MUST enable access control to limit queries to your legitimate users. Failing to do so will cause your server to become part of large scale DNS amplification attacks. Implementing BCP38 within your network would greatly reduce such attack surface */ recursion yes; dnssec-enable yes; dnssec-validation yes; /* Path to ISC DLV key */ bindkeys-file "/etc/named.root.key"; managed-keys-directory "/var/named/dynamic"; pid-file "/run/named/named.pid"; session-keyfile "/run/named/session.key"; tkey-gssapi-keytab "/etc/dns.keytab"; }; logging { channel default_debug { file "data/named.run"; severity dynamic; }; }; zone "." IN { type hint; file "named.ca"; }; zone "example.com." IN { type master; file "dynamic/db.example.com"; notify no; update-policy { grant EXAMPLE.COM krb5-self . A AAAA; grant test@EXAMPLE.COM wildcard * ANY; }; }; zone "10.168.192.in-addr.arpa." IN { type master; file "dynamic/db.10.168.192.in-addr.arpa"; notify no; update-policy { grant * tcp-self . PTR; }; }; include "/etc/named.rfc1912.zones"; include "/etc/named.root.key"; golang-github-bodgit-tsig-1.3.1/tsig.go000066400000000000000000000002541520706045100177530ustar00rootroot00000000000000/* Package tsig adds support for additional TSIG methods used in DNS queries. */ package tsig const ( // GSS is the RFC 3645 defined algorithm name. GSS = "gss-tsig." )