pax_global_header00006660000000000000000000000064152070605310014511gustar00rootroot0000000000000052 comment=c5ee807f550da37b2f85edfd05f95788e7762c85 golang-github-bodgit-gssapi-0.0.4/000077500000000000000000000000001520706053100167735ustar00rootroot00000000000000golang-github-bodgit-gssapi-0.0.4/.github/000077500000000000000000000000001520706053100203335ustar00rootroot00000000000000golang-github-bodgit-gssapi-0.0.4/.github/renovate.json000066400000000000000000000003441520706053100230520ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended", "docker:pinDigests" ], "pre-commit": { "enabled": true }, "postUpdateOptions": [ "gomodTidy" ] } golang-github-bodgit-gssapi-0.0.4/.github/workflows/000077500000000000000000000000001520706053100223705ustar00rootroot00000000000000golang-github-bodgit-gssapi-0.0.4/.github/workflows/build.yml000066400000000000000000000054421520706053100242170ustar00rootroot00000000000000name: 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: TEST_HOST: host.example.com TEST_REALM: EXAMPLE.COM TEST_USERNAME: test TEST_PASSWORD: password TEST_KEYTAB: ${{ github.workspace }}/testdata/test.keytab KRB5_CLIENT_KTNAME: ${{ github.workspace }}/testdata/test.keytab KRB5_CONFIG: ${{ github.workspace }}/testdata/krb5.conf KRB5_KTNAME: ${{ github.workspace }}/testdata/host.keytab steps: - name: Checkout uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: 'go.mod' - name: golangci-lint uses: golangci/golangci-lint-action@v9 if: github.event_name == 'pull_request' with: only-new-issues: true - name: Install Kerberos client run: | sudo apt-get update sudo DEBIAN_FRONTEND=noninteractive apt-get install -yq libkrb5-dev krb5-user - 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@v7 with: context: "{{defaultContext}}:testdata" load: true tags: kdc target: kdc cache-from: type=gha cache-to: type=gha,mode=max - name: Extract keytab uses: docker/build-push-action@v7 with: context: "{{defaultContext}}:testdata" outputs: type=local,dest=testdata target: keytab - name: Pull containers into Podman run: | podman pull docker-daemon:kdc: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 echo $TEST_PASSWORD | KRB5_TRACE=/dev/stdout kinit ${TEST_USERNAME}@${TEST_REALM} - name: Test run: go test -v -coverprofile=cover.out ./... - name: Send coverage uses: shogo82148/actions-goveralls@v1 with: path-to-profile: cover.out golang-github-bodgit-gssapi-0.0.4/.github/workflows/cleanup.yml000066400000000000000000000015201520706053100245400ustar00rootroot00000000000000name: 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-gssapi-0.0.4/.github/workflows/pr-lint.yml000066400000000000000000000022321520706053100244770ustar00rootroot00000000000000name: 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-gssapi-0.0.4/.github/workflows/release.yml000066400000000000000000000005001520706053100245260ustar00rootroot00000000000000--- 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-gssapi-0.0.4/.gitignore000066400000000000000000000000361520706053100207620ustar00rootroot00000000000000/cover.out /testdata/*.keytab golang-github-bodgit-gssapi-0.0.4/.golangci.yaml000066400000000000000000000036441520706053100215270ustar00rootroot00000000000000version: "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-gssapi-0.0.4/.pre-commit-config.yaml000066400000000000000000000003351520706053100232550ustar00rootroot00000000000000repos: - 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-gssapi-0.0.4/.release-please-manifest.json000066400000000000000000000000161520706053100244340ustar00rootroot00000000000000{".":"0.0.4"} golang-github-bodgit-gssapi-0.0.4/CHANGELOG.md000066400000000000000000000033211520706053100206030ustar00rootroot00000000000000# Changelog ## [0.0.4](https://github.com/bodgit/gssapi/compare/v0.0.3...v0.0.4) (2026-05-28) ### Bug Fixes * Refactor away multierror ([#74](https://github.com/bodgit/gssapi/issues/74)) ([841d5c4](https://github.com/bodgit/gssapi/commit/841d5c4c9c555ec5fa06c18d030d098a7583de5c)) ## [0.0.3](https://github.com/bodgit/gssapi/compare/v0.0.2...v0.0.3) (2026-05-19) ### Bug Fixes * **deps:** update module github.com/go-logr/logr to v1.4.3 ([#56](https://github.com/bodgit/gssapi/issues/56)) ([0876c1e](https://github.com/bodgit/gssapi/commit/0876c1e9eb233d5018db60235151c694bace7545)) * **deps:** update module github.com/spf13/afero to v1.15.0 ([#59](https://github.com/bodgit/gssapi/issues/59)) ([9ff1c9d](https://github.com/bodgit/gssapi/commit/9ff1c9da529ed1950f9eaddd81aa5e6a4dbfbe7f)) * **deps:** update module github.com/stretchr/testify to v1.11.1 ([#60](https://github.com/bodgit/gssapi/issues/60)) ([91bbdb7](https://github.com/bodgit/gssapi/commit/91bbdb7a3ee43dd80c6ee67502937f757351450b)) * use acceptor subkey for initiator signing when present. Fixes bodgit/tsig[#178](https://github.com/bodgit/gssapi/issues/178) ([#50](https://github.com/bodgit/gssapi/issues/50)) ([72218f2](https://github.com/bodgit/gssapi/commit/72218f20a58b8aa4afe2cf38cb0ff8b5424bdfe7)) ## [0.0.2](https://github.com/bodgit/gssapi/compare/v0.0.1...v0.0.2) (2023-10-01) ### Features * Track expiry ([#19](https://github.com/bodgit/gssapi/issues/19)) ([d8f6d34](https://github.com/bodgit/gssapi/commit/d8f6d343a1ddba12fa217cf9d82eaaa8e010bc7b)) ## 0.0.1 (2023-09-13) ### Bug Fixes * Initial release ([#12](https://github.com/bodgit/gssapi/issues/12)) ([fcf9a1f](https://github.com/bodgit/gssapi/commit/fcf9a1f3a08b0baf0d4cbef93a3d47ed3b130c32)) golang-github-bodgit-gssapi-0.0.4/LICENSE000066400000000000000000000027501520706053100200040ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2023, 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-gssapi-0.0.4/README.md000066400000000000000000000045431520706053100202600ustar00rootroot00000000000000[![GitHub release](https://img.shields.io/github/v/release/bodgit/gssapi)](https://github.com/bodgit/gssapi/releases) [![Build Status](https://img.shields.io/github/actions/workflow/status/bodgit/gssapi/build.yml?branch=main)](https://github.com/bodgit/gssapi/actions?query=workflow%3ABuild) [![Coverage Status](https://coveralls.io/repos/github/bodgit/gssapi/badge.svg?branch=main)](https://coveralls.io/github/bodgit/gssapi?branch=main) [![Go Report Card](https://goreportcard.com/badge/github.com/bodgit/gssapi)](https://goreportcard.com/report/github.com/bodgit/gssapi) [![GoDoc](https://godoc.org/github.com/bodgit/gssapi?status.svg)](https://godoc.org/github.com/bodgit/gssapi) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/bodgit/gssapi) # GSSAPI wrapper for gokrb5 The [github.com/bodgit/gssapi](https://godoc.org/github.com/bodgit/gssapi) package implements a GSSAPI-like wrapper around the [github.com/jcmturner/gokrb5](https://github.com/jcmturner/gokrb5) package. Sample Initiator (Client): ```golang package main import ( . "github.com/bodgit/gssapi" "github.com/jcmturner/gokrb5/v8/gssapi" ) func main() { initiator, err := NewInitiator(WithRealm("EXAMPLE.COM"), WithUsername("test"), WithKeytab[Initiator]("test.keytab")) if err != nil { panic(err) } defer initiator.Close() output, cont, err := initiator.Initiate("host/ssh.example.com", gssapi.ContextFlagInteg|gssapi.ContextFlagMutual, nil) if err != nil { panic(err) } // transmit output to Acceptor signature, err := initiator.MakeSignature(message) if err != nil { panic(err) } // transmit message and signature to Acceptor } ``` Sample Acceptor (Server): ```golang package main import ( . "github.com/bodgit/gssapi" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/types" ) func main() { principal := types.NewPrincipalName(nametype.KRB_NT_SRV_HST, "host/ssh.example.com") acceptor, err := NewAcceptor(WithServicePrincipal(&principal)) if err != nil { panic(err) } defer acceptor.Close() // receive input from Initiator output, cont, err := acceptor.Accept(input) if err != nil { panic(err) } // transmit output back to Initiator // receive message and signature from Initiator if err := acceptor.VerifySignature(message, signature); err != nil { panic(err) } } ``` golang-github-bodgit-gssapi-0.0.4/acceptor.go000066400000000000000000000125051520706053100211250ustar00rootroot00000000000000package gssapi import ( "crypto/rand" "encoding/binary" "encoding/hex" "errors" "fmt" "math" "math/big" "time" "github.com/go-logr/logr" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/iana/errorcode" ianaflags "github.com/jcmturner/gokrb5/v8/iana/flags" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/spnego" "github.com/jcmturner/gokrb5/v8/types" ) // Acceptor represents the server side of the GSSAPI protocol. type Acceptor struct { context keytab string principal *types.PrincipalName clockSkew time.Duration logger logr.Logger } // NewAcceptor returns a new Acceptor. func NewAcceptor(options ...Option[Acceptor]) (*Acceptor, error) { ctx := &Acceptor{ context: context{ acceptor: true, sequenceMask: math.MaxUint32, logger: logr.Discard(), }, clockSkew: 10 * time.Second, logger: logr.Discard(), } var err error for _, option := range options { if err = option(ctx); err != nil { return nil, err } } return ctx, nil } // Close releases any resources held by the Acceptor. func (ctx *Acceptor) Close() error { return nil } func verifyAPReq(apreq *messages.APReq, kt *keytab.Keytab, skew time.Duration, sname *types.PrincipalName) error { err := apreq.Ticket.DecryptEncPart(kt, sname) if _, ok := err.(messages.KRBError); ok { //nolint:errorlint return err } else if err != nil { return messages.NewKRBError(apreq.Ticket.SName, apreq.Ticket.Realm, errorcode.KRB_AP_ERR_BAD_INTEGRITY, "could not decrypt ticket") } if _, err := apreq.Ticket.Valid(skew); err != nil { return err } if err := apreq.DecryptAuthenticator(apreq.Ticket.DecryptedEncPart.Key); err != nil { return messages.NewKRBError(apreq.Ticket.SName, apreq.Ticket.Realm, errorcode.KRB_AP_ERR_BAD_INTEGRITY, "could not decrypt authenticator") } if !apreq.Authenticator.CName.Equal(apreq.Ticket.DecryptedEncPart.CName) { return messages.NewKRBError(apreq.Ticket.SName, apreq.Ticket.Realm, errorcode.KRB_AP_ERR_BADMATCH, "CName in Authenticator does not match that in service ticket") } if time.Now().UTC().Sub( apreq.Authenticator.CTime.Add( time.Duration(apreq.Authenticator.Cusec)*time.Microsecond)).Abs() > skew { return messages.NewKRBError(apreq.Ticket.SName, apreq.Ticket.Realm, errorcode.KRB_AP_ERR_SKEW, fmt.Sprintf("clock skew with client too large, greater than %v seconds", skew)) } return nil } func getAPRepMessage(tkt messages.Ticket, key types.EncryptionKey, ctime time.Time, cusec int) (*apRep, uint64, error) { seq, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32)) if err != nil { return nil, 0, err } seqNum := seq.Int64() & 0x3fffffff encPart := encAPRepPart{ CTime: ctime, Cusec: cusec, SequenceNumber: seqNum, } aprep, err := newAPRep(tkt, key, encPart) if err != nil { return nil, 0, fmt.Errorf("gssapi: %w", err) } return &aprep, uint64(seqNum), nil } // Accept responds to the token from the Initiator, returning a token to be // sent back to the Initiator and whether another round is required. // //nolint:cyclop,funlen func (ctx *Acceptor) Accept(input []byte) ([]byte, bool, error) { if ctx.established { return nil, false, nil } var apreq spnego.KRB5Token if err := apreq.Unmarshal(input); err != nil { return nil, false, err } if apreq.IsKRBError() { return nil, false, errors.New("received kerberos error") } // FIXME check invalid token ID if !apreq.IsAPReq() { return nil, false, errors.New("didn't receive an AP-REQ") } kt, err := loadKeytab(ctx.logger) if err != nil { return nil, false, err } var output []byte // if _, err := apreq.APReq.Verify(kt, ctx.clockSkew, FIXME, nil); err != nil { if err = verifyAPReq(&apreq.APReq, kt, ctx.clockSkew, ctx.principal); err != nil { var krbError messages.KRBError if errors.As(err, &krbError) { tb, _ := hex.DecodeString(spnego.TOK_ID_KRB_ERROR) m := krb5Token{ oid: gssapi.OIDKRB5.OID(), tokID: tb, krbError: &krbError, } if output, err = m.marshal(); err == nil { return output, true, nil } } return nil, false, err } ctx.baseSequenceNumber = uint64(apreq.APReq.Authenticator.SeqNumber) ctx.ctime = apreq.APReq.Authenticator.CTime ctx.cusec = apreq.APReq.Authenticator.Cusec ctx.key = apreq.APReq.Ticket.DecryptedEncPart.Key if apreq.APReq.Authenticator.SubKey.KeyType != 0 { ctx.peerSubkey = apreq.APReq.Authenticator.SubKey } ctx.flags = int(supportedFlags & binary.LittleEndian.Uint32(apreq.APReq.Authenticator.Cksum.Checksum[20:24])) ctx.expiry = apreq.APReq.Ticket.DecryptedEncPart.EndTime ctx.peerName = fmt.Sprintf("%s@%s", apreq.APReq.Ticket.DecryptedEncPart.CName.PrincipalNameString(), apreq.APReq.Ticket.DecryptedEncPart.CRealm) if types.IsFlagSet(&apreq.APReq.APOptions, ianaflags.APOptionMutualRequired) { var aprep *apRep aprep, ctx.sequenceNumber, err = getAPRepMessage(apreq.APReq.Ticket, ctx.key, ctx.ctime, ctx.cusec) if err != nil { return nil, false, err } tb, _ := hex.DecodeString(spnego.TOK_ID_KRB_AP_REP) m := krb5Token{ oid: gssapi.OIDKRB5.OID(), tokID: tb, apRep: aprep, } output, err = m.marshal() if err != nil { return nil, false, err } } else { ctx.sequenceNumber = ctx.baseSequenceNumber } ctx.established = true return output, false, nil } golang-github-bodgit-gssapi-0.0.4/aprep.go000066400000000000000000000040071520706053100204320ustar00rootroot00000000000000package gssapi import ( "time" "github.com/jcmturner/gofork/encoding/asn1" "github.com/jcmturner/gokrb5/v8/asn1tools" "github.com/jcmturner/gokrb5/v8/crypto" "github.com/jcmturner/gokrb5/v8/iana" "github.com/jcmturner/gokrb5/v8/iana/asnAppTag" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/iana/msgtype" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/types" ) // These are a 1:1 copy of the types from github.com/jcmturner/gokrb5/v8 // with marshalling methods added. If/when upstream adds the missing methods // these can be removed. type apRep struct { PVNO int `asn1:"explicit,tag:0"` MsgType int `asn1:"explicit,tag:1"` EncPart types.EncryptedData `asn1:"explicit,tag:2"` } func (a *apRep) marshal() ([]byte, error) { b, err := asn1.Marshal(*a) if err != nil { return nil, err } return asn1tools.AddASNAppTag(b, asnAppTag.APREP), nil } type encAPRepPart struct { CTime time.Time `asn1:"generalized,explicit,tag:0"` Cusec int `asn1:"explicit,tag:1"` Subkey types.EncryptionKey `asn1:"optional,explicit,tag:2"` SequenceNumber int64 `asn1:"optional,explicit,tag:3"` } func (a *encAPRepPart) marshal() ([]byte, error) { b, err := asn1.Marshal(*a) if err != nil { return nil, err } return asn1tools.AddASNAppTag(b, asnAppTag.EncAPRepPart), nil } func newAPRep(tkt messages.Ticket, sessionKey types.EncryptionKey, encPart encAPRepPart) (apRep, error) { m, err := encPart.marshal() if err != nil { return apRep{}, krberror.Errorf(err, krberror.EncodingError, "marshaling error of AP-REP enc-part") } ed, err := crypto.GetEncryptedData(m, sessionKey, keyusage.AP_REP_ENCPART, tkt.EncPart.KVNO) if err != nil { return apRep{}, krberror.Errorf(err, krberror.EncryptingError, "error encrypting AP-REP enc-part") } return apRep{ PVNO: iana.PVNO, MsgType: msgtype.KRB_AP_REP, EncPart: ed, }, nil } golang-github-bodgit-gssapi-0.0.4/context.go000066400000000000000000000100051520706053100210020ustar00rootroot00000000000000package gssapi import ( "errors" "time" "github.com/go-logr/logr" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/types" ) var ( errDuplicateToken = errors.New("duplicate per-message token detected") errOldToken = errors.New("timed-out per-message token detected") errUnseqToken = errors.New("reordered (early) per-message token detected") errGapToken = errors.New("skipped predecessor token(s) detected") ) type context struct { acceptor bool established bool key types.EncryptionKey subkey types.EncryptionKey peerSubkey types.EncryptionKey flags int ctime time.Time cusec int expiry time.Time peerName string sequenceNumber uint64 baseSequenceNumber uint64 nextSequenceNumber uint64 receiveMask uint64 sequenceMask uint64 logger logr.Logger } func (ctx *context) hasSubkey() bool { return ctx.subkey.KeyType != 0 } func (ctx *context) hasPeerSubkey() bool { return ctx.peerSubkey.KeyType != 0 } func (ctx *context) doMutual() bool { return ctx.flags&gssapi.ContextFlagMutual != 0 } func (ctx *context) doReplay() bool { return ctx.flags&gssapi.ContextFlagReplay != 0 } func (ctx *context) doSequence() bool { return ctx.flags&gssapi.ContextFlagSequence != 0 } //nolint:cyclop func (ctx *context) checkSequenceNumber(sequenceNumber uint64) error { if !ctx.doReplay() && !ctx.doSequence() { return nil } relativeSequenceNumber := (sequenceNumber - ctx.baseSequenceNumber) & ctx.sequenceMask if relativeSequenceNumber >= ctx.nextSequenceNumber { offset := relativeSequenceNumber - ctx.nextSequenceNumber ctx.receiveMask = ctx.receiveMask<<(offset+1) | 1 ctx.nextSequenceNumber = (relativeSequenceNumber + 1) & ctx.sequenceMask if offset > 0 && ctx.doSequence() { return errGapToken } return nil } offset := ctx.nextSequenceNumber - relativeSequenceNumber if offset > 64 { if ctx.doSequence() { return errUnseqToken } return errOldToken } bit := uint64(1) << (offset - 1) if ctx.doReplay() && ctx.receiveMask&bit != 0 { return errDuplicateToken } ctx.receiveMask |= bit if ctx.doSequence() { return errUnseqToken } return nil } // PeerName returns the peer Kerberos principal. func (ctx *context) PeerName() string { return ctx.peerName } // Established returns the context state. func (ctx *context) Established() bool { return ctx.established } // Expiry returns the ticket expiry for the context. func (ctx *context) Expiry() time.Time { return ctx.expiry } // MakeSignature creates a MIC token against the provided input. func (ctx *context) MakeSignature(message []byte) ([]byte, error) { var ( flags byte usage uint32 = keyusage.GSSAPI_INITIATOR_SIGN ) if ctx.acceptor { flags |= gssapi.MICTokenFlagSentByAcceptor usage = keyusage.GSSAPI_ACCEPTOR_SIGN } key := ctx.key if ctx.hasSubkey() { key = ctx.subkey if ctx.acceptor { flags |= gssapi.MICTokenFlagAcceptorSubkey } } else if ctx.hasPeerSubkey() { key = ctx.peerSubkey flags |= gssapi.MICTokenFlagAcceptorSubkey } token := gssapi.MICToken{ Flags: flags, SndSeqNum: ctx.sequenceNumber, Payload: message, } if err := token.SetChecksum(key, usage); err != nil { return nil, err } signature, err := token.Marshal() if err != nil { return nil, err } ctx.sequenceNumber++ return signature, nil } // VerifySignature verifies the MIC token against the provided input. func (ctx *context) VerifySignature(message, signature []byte) error { var ( token gssapi.MICToken err error ) if err = token.Unmarshal(signature, !ctx.acceptor); err != nil { return err } token.Payload = message if err = ctx.checkSequenceNumber(token.SndSeqNum); err != nil { return err } var usage uint32 = keyusage.GSSAPI_ACCEPTOR_SIGN if ctx.acceptor { usage = keyusage.GSSAPI_INITIATOR_SIGN } key := ctx.key if ctx.hasPeerSubkey() { key = ctx.peerSubkey } if _, err = token.Verify(key, usage); err != nil { return err } return nil } golang-github-bodgit-gssapi-0.0.4/files.go000066400000000000000000000037061520706053100204320ustar00rootroot00000000000000package gssapi import ( "errors" "fmt" "os" "strings" "github.com/go-logr/logr" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/credentials" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/spf13/afero" ) const ( krb5FilePrefix = "FILE:" krb5Config = "KRB5_CONFIG" krb5CCName = "KRB5CCNAME" krb5KTName = "KRB5_KTNAME" krb5ClientKTName = "KRB5_CLIENT_KTNAME" ) //nolint:gochecknoglobals var fs = afero.NewOsFs() func findFile(logger logr.Logger, env string, try []string) (string, error) { logger.Info("looking for file", "env", env, "paths", try) path, ok := os.LookupEnv(env) if ok { path = strings.TrimPrefix(path, krb5FilePrefix) if _, err := fs.Stat(path); err != nil { return "", fmt.Errorf("%s: %w", env, err) } return path, nil } errs := fmt.Errorf("%s: not found", env) for _, t := range try { if _, err := fs.Stat(t); err != nil { errs = errors.Join(errs, err) if os.IsNotExist(err) { continue } return "", errs } return t, nil } return "", errs } func loadConfig(logger logr.Logger) (*config.Config, error) { path, err := findFile(logger, krb5Config, []string{"/etc/krb5.conf"}) if err != nil { return nil, err } return config.Load(path) } func loadCCache(logger logr.Logger) (*credentials.CCache, error) { path, err := findFile(logger, krb5CCName, []string{fmt.Sprintf("/tmp/krb5cc_%d", os.Getuid())}) if err != nil { return nil, err } return credentials.LoadCCache(path) } func loadKeytab(logger logr.Logger) (*keytab.Keytab, error) { path, err := findFile(logger, krb5KTName, []string{"/etc/krb5.keytab"}) if err != nil { return nil, err } return keytab.Load(path) } func loadClientKeytab(logger logr.Logger) (*keytab.Keytab, error) { path, err := findFile(logger, krb5ClientKTName, []string{fmt.Sprintf("/var/kerberos/krb5/user/%d/client.keytab", os.Geteuid())}) if err != nil { return nil, err } return keytab.Load(path) } golang-github-bodgit-gssapi-0.0.4/files_internal_test.go000066400000000000000000000052631520706053100233650ustar00rootroot00000000000000package gssapi import ( "errors" iofs "io/fs" "os" "testing" "github.com/go-logr/logr/testr" "github.com/spf13/afero" "github.com/stretchr/testify/assert" ) const findFileEnvironment = "FIND_FILE" var errStatError = errors.New("stat error") type statErrorFs struct { afero.Fs } func (statErrorFs) Stat(_ string) (os.FileInfo, error) { return nil, errStatError } //nolint:funlen,paralleltest func TestFindFile(t *testing.T) { tables := []struct { name string fs afero.Fs files []string env string try []string result string err error }{ { "goodenv", afero.NewMemMapFs(), []string{"/foo"}, "/foo", []string{}, "/foo", nil, }, { "first", afero.NewMemMapFs(), []string{"/foo"}, "", []string{"/foo", "/bar"}, "/foo", nil, }, { "second", afero.NewMemMapFs(), []string{"/bar"}, "", []string{"/foo", "/bar"}, "/bar", nil, }, { "badenv", afero.NewMemMapFs(), []string{"/bar"}, "/foo", []string{}, "", iofs.ErrNotExist, }, { "badstat", statErrorFs{afero.NewMemMapFs()}, []string{"/foo"}, "", []string{"/foo"}, "", errStatError, }, { "none", afero.NewMemMapFs(), []string{}, "", []string{"/foo"}, "", iofs.ErrNotExist, }, } //nolint:paralleltest for _, table := range tables { t.Run(table.name, func(t *testing.T) { oldFs := fs defer func() { fs = oldFs }() fs = table.fs for _, file := range table.files { if err := afero.WriteFile(fs, file, nil, 0); err != nil { t.Fatal(err) } } if table.env != "" { t.Setenv(findFileEnvironment, table.env) } result, err := findFile(testr.New(t), findFileEnvironment, table.try) assert.Equal(t, table.result, result) assert.ErrorIs(t, err, table.err) }) } } //nolint:paralleltest func TestLoadConfig(t *testing.T) { oldFs := fs defer func() { fs = oldFs }() fs = statErrorFs{afero.NewMemMapFs()} _, err := loadConfig(testr.New(t)) assert.ErrorIs(t, err, errStatError) } //nolint:paralleltest func TestLoadCCache(t *testing.T) { oldFs := fs defer func() { fs = oldFs }() fs = statErrorFs{afero.NewMemMapFs()} _, err := loadCCache(testr.New(t)) assert.ErrorIs(t, err, errStatError) } //nolint:paralleltest func TestLoadKeytab(t *testing.T) { oldFs := fs defer func() { fs = oldFs }() fs = statErrorFs{afero.NewMemMapFs()} _, err := loadKeytab(testr.New(t)) assert.ErrorIs(t, err, errStatError) } //nolint:paralleltest func TestLoadClientKeytab(t *testing.T) { oldFs := fs defer func() { fs = oldFs }() fs = statErrorFs{afero.NewMemMapFs()} _, err := loadClientKeytab(testr.New(t)) assert.ErrorIs(t, err, errStatError) } golang-github-bodgit-gssapi-0.0.4/go.mod000066400000000000000000000013611520706053100201020ustar00rootroot00000000000000module github.com/bodgit/gssapi go 1.23.0 require ( github.com/go-logr/logr v1.4.3 github.com/jcmturner/gofork v1.7.6 github.com/jcmturner/gokrb5/v8 v8.4.4 github.com/spf13/afero v1.15.0 github.com/stretchr/testify v1.11.1 ) require ( github.com/davecgh/go-spew v1.1.1 // 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/goidentity/v6 v6.0.1 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/crypto v0.16.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/text v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-bodgit-gssapi-0.0.4/go.sum000066400000000000000000000160601520706053100201310ustar00rootroot00000000000000github.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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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/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/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.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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/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/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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 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/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-gssapi-0.0.4/gssapi.go000066400000000000000000000005111520706053100206050ustar00rootroot00000000000000/* Package gssapi implements a simplified wrapper around the github.com/jcmturner/gokrb5 package. */ package gssapi import "github.com/jcmturner/gokrb5/v8/gssapi" const ( supportedFlags = gssapi.ContextFlagMutual | gssapi.ContextFlagReplay | gssapi.ContextFlagSequence | gssapi.ContextFlagConf | gssapi.ContextFlagInteg ) golang-github-bodgit-gssapi-0.0.4/gssapi_test.go000066400000000000000000000117341520706053100216550ustar00rootroot00000000000000//nolint:revive package gssapi_test import ( "errors" "fmt" "os" "path/filepath" "testing" "time" . "github.com/bodgit/gssapi" "github.com/go-logr/logr/testr" "github.com/jcmturner/gokrb5/v8/gssapi" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/types" "github.com/stretchr/testify/assert" ) func environmentVariables(t *testing.T) (string, string, string, string, string) { t.Helper() var ( host string realm string username string password string keytab string ok bool err error ) for _, env := range []struct { ptr *string name string }{ { &host, "TEST_HOST", }, { &realm, "TEST_REALM", }, { &username, "TEST_USERNAME", }, { &password, "TEST_PASSWORD", }, { &keytab, "TEST_KEYTAB", }, } { if *env.ptr, ok = os.LookupEnv(env.name); !ok { err = errors.Join(err, fmt.Errorf("%s is not set", env.name)) } } if err != nil { t.Fatal(err) } return host, realm, username, password, keytab } //nolint:cyclop,funlen,lll func testExchange(t *testing.T, service string, mutual bool, initiatorOptions []Option[Initiator], acceptorOptions []Option[Acceptor]) { t.Helper() flags := gssapi.ContextFlagInteg | gssapi.ContextFlagReplay | gssapi.ContextFlagSequence if mutual { flags |= gssapi.ContextFlagMutual } c, err := NewInitiator(initiatorOptions...) if err != nil { t.Fatal(err) } defer func() { err = c.Close() }() s, err := NewAcceptor(acceptorOptions...) if err != nil { t.Fatal(err) } defer func() { err = s.Close() }() output, cont, err := c.Initiate(service, flags, nil) if err != nil { t.Fatal(err) } assert.Greater(t, len(output), 0) assert.True(t, cont) assert.WithinRange(t, c.Expiry(), time.Now().Add(1439*time.Minute), time.Now().Add(1441*time.Minute)) input, cont, err := s.Accept(output) if err != nil { t.Fatal(err) } if mutual { assert.False(t, c.Established()) assert.Greater(t, len(input), 0) } else { assert.True(t, c.Established()) assert.Equal(t, len(input), 0) } assert.False(t, cont) assert.True(t, s.Established()) assert.WithinRange(t, s.Expiry(), time.Now().Add(1439*time.Minute), time.Now().Add(1441*time.Minute)) if mutual { output, cont, err = c.Initiate(service, flags, input) if err != nil { t.Fatal(err) } assert.Equal(t, len(output), 0) assert.False(t, cont) assert.True(t, c.Established()) } message := []byte("test message") signature, err := c.MakeSignature(message) if err != nil { t.Fatal(err) } if err = s.VerifySignature(message, signature); err != nil { t.Fatal(err) } signature, err = s.MakeSignature(message) if err != nil { t.Fatal(err) } if err = c.VerifySignature(message, signature); err != nil { t.Fatal(err) } } //nolint:funlen func TestExchange(t *testing.T) { t.Parallel() if testing.Short() { t.Skip("skipping integration test") } logger := testr.New(t) host, realm, username, password, keytab := environmentVariables(t) service := "host/" + host principal := types.NewPrincipalName(nametype.KRB_NT_SRV_HST, service) config, err := os.ReadFile(filepath.Join("testdata", "krb5.conf")) if err != nil { t.Fatal(err) } tables := []struct { name string mutual bool initiatorOptions []Option[Initiator] acceptorOptions []Option[Acceptor] }{ { "session", false, []Option[Initiator]{ WithLogger[Initiator](logger), WithConfig(string(config)), }, []Option[Acceptor]{ WithLogger[Acceptor](logger), WithServicePrincipal(&principal), WithClockSkew(5 * time.Second), }, }, { "mutual", true, []Option[Initiator]{ WithLogger[Initiator](logger), WithConfig(string(config)), }, []Option[Acceptor]{ WithLogger[Acceptor](logger), WithServicePrincipal(&principal), WithClockSkew(5 * time.Second), }, }, { "password", true, []Option[Initiator]{ WithLogger[Initiator](logger), WithRealm(realm), WithUsername(username), WithPassword(password), }, []Option[Acceptor]{ WithLogger[Acceptor](logger), WithServicePrincipal(&principal), WithClockSkew(5 * time.Second), }, }, { "keytab", true, []Option[Initiator]{ WithLogger[Initiator](logger), WithRealm(realm), WithUsername(username), WithKeytab[Initiator](keytab), }, []Option[Acceptor]{ WithLogger[Acceptor](logger), WithServicePrincipal(&principal), WithClockSkew(5 * time.Second), }, }, { "keytab2", true, []Option[Initiator]{ WithLogger[Initiator](logger), WithRealm(realm), WithUsername(username), WithKeytab[Initiator](""), }, []Option[Acceptor]{ WithLogger[Acceptor](logger), WithServicePrincipal(&principal), WithClockSkew(5 * time.Second), }, }, } for _, table := range tables { table := table t.Run(table.name, func(t *testing.T) { t.Parallel() testExchange(t, service, table.mutual, table.initiatorOptions, table.acceptorOptions) }) } } golang-github-bodgit-gssapi-0.0.4/initiator.go000066400000000000000000000126441520706053100213330ustar00rootroot00000000000000package gssapi import ( "errors" "fmt" "math" "math/bits" "strings" "time" "github.com/go-logr/logr" "github.com/jcmturner/gokrb5/v8/client" "github.com/jcmturner/gokrb5/v8/config" "github.com/jcmturner/gokrb5/v8/crypto" ianaflags "github.com/jcmturner/gokrb5/v8/iana/flags" "github.com/jcmturner/gokrb5/v8/iana/keyusage" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/krberror" "github.com/jcmturner/gokrb5/v8/messages" "github.com/jcmturner/gokrb5/v8/spnego" "github.com/jcmturner/gokrb5/v8/types" ) // Initiator represents the client side of the GSSAPI protocol. type Initiator struct { context config string domain string username string password string keytab *string client *client.Client logger logr.Logger } func (ctx *Initiator) loadConfig() (*config.Config, error) { if ctx.config != "" { return config.NewFromString(ctx.config) } return loadConfig(ctx.logger) } func (ctx *Initiator) usePassword() bool { return ctx.domain != "" && ctx.username != "" && ctx.password != "" } func (ctx *Initiator) useKeytab() bool { return ctx.domain != "" && ctx.username != "" && ctx.keytab != nil } func (ctx *Initiator) newClient() (*client.Client, error) { cfg, err := ctx.loadConfig() if err != nil { return nil, err } settings := []func(*client.Settings){ client.DisablePAFXFAST(true), } switch { case ctx.usePassword(): return client.NewWithPassword(ctx.username, ctx.domain, ctx.password, cfg, settings...), nil case ctx.useKeytab(): var kt *keytab.Keytab if *ctx.keytab != "" { kt, err = keytab.Load(*ctx.keytab) } else { kt, err = loadClientKeytab(ctx.logger) } if err != nil { return nil, err } return client.NewWithKeytab(ctx.username, ctx.domain, kt, cfg, settings...), nil } ctx.logger.Info("using default session") cache, err := loadCCache(ctx.logger) if err != nil { return nil, err } return client.NewFromCCache(cache, cfg, settings...) } // NewInitiator returns a new Initiator. func NewInitiator(options ...Option[Initiator]) (*Initiator, error) { ctx := &Initiator{ context: context{ sequenceMask: math.MaxUint32, logger: logr.Discard(), }, logger: logr.Discard(), } var err error for _, option := range options { if err = option(ctx); err != nil { return nil, err } } if ctx.client, err = ctx.newClient(); err != nil { return nil, err } if err = ctx.client.AffirmLogin(); err != nil { return nil, err } return ctx, nil } // Close releases any resources held by the Initiator. func (ctx *Initiator) Close() error { ctx.client.Destroy() return nil } // Initiate creates a new context targeting the service with the desired flags // along with the initial input token, which will initially be nil. The output // token is returned and whether another round is required. // //nolint:cyclop,funlen func (ctx *Initiator) Initiate(service string, flags int, input []byte) ([]byte, bool, error) { if ctx.established { return nil, false, nil } var err error //nolint:nestif if len(input) == 0 { ctx.flags = flags & supportedFlags // BUG(bodgit): see https://github.com/jcmturner/gokrb5/issues/529 ctx.expiry = time.Now().Add(ctx.client.Config.LibDefaults.TicketLifetime) var ticket messages.Ticket if ticket, ctx.key, err = ctx.client.GetServiceTicket(strings.ReplaceAll(service, "@", "/")); err != nil { return nil, false, err } ctx.peerName = fmt.Sprintf("%s@%s", ticket.SName.PrincipalNameString(), ticket.Realm) f := make([]int, 0, bits.OnesCount(uint(ctx.flags))) for i := 0; i < bits.Len(supportedFlags); i++ { if ctx.flags&(1<