krb5ssp,ldap: migrate to gokrb5/v9 and adapt to decoupled ldap/v3

go-smb and github.com/jfjallid/ldap/v3 were mutually dependent modules:
go-smb/ldap wraps ldap/v3, while ldap/v3 reached back into go-smb/{gss,
krb5ssp,ntlmssp}. Because krb5ssp.KRB5Token carries gokrb5 types across that
boundary, the two were also pinned to the same gokrb5 major version. ldap/v3
v3.101.0 severs that cycle (it no longer imports go-smb); this is the go-smb
side of the change.

krb5ssp:
- Migrate from gokrb5/v8 to gokrb5/v9 (krb5ssp.go, wrapToken.go, the
  ccache_{linux,darwin,windows}.go ccache loaders and tests). No behaviour
  change; the v9 API is source-compatible for the surface we use.

ldap:
- ldap/v3 now inverts its NTLM dependency instead of importing go-smb/ntlmssp.
  Add ldap/ntlm.go: an ntlmssp.Client-backed adapter implementing ldap's
  NTLMNegotiator plus the optional NTLMChannelBinder and NTLMSessionProvider
  capabilities, registered as ldap.NTLMNegotiatorFactory in init(). Importing
  go-smb/ldap therefore restores out-of-the-box NTLM binds; the wrapper's
  credential-based NTLMChallengeBind path is unchanged and now resolves the
  negotiator through this factory.

go.mod:
- gokrb5/v8 -> gokrb5/v9 v9.0.0; ldap/v3 v3.100.0 -> v3.101.0. gokrb5 is now an
  ordinary shared dependency resolved by MVS.
This commit is contained in:
Jimmy Fjällid
2026-06-06 16:10:06 +02:00
parent cb9f559549
commit 0c97d2f64a
9 changed files with 131 additions and 27 deletions
+2 -2
View File
@@ -7,9 +7,9 @@ require golang.org/x/crypto v0.48.0
require (
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667
github.com/jfjallid/gofork v1.7.6
github.com/jfjallid/gokrb5/v8 v8.6.3
github.com/jfjallid/gokrb5/v9 v9.0.0
github.com/jfjallid/golog v0.3.6
github.com/jfjallid/ldap/v3 v3.100.0
github.com/jfjallid/ldap/v3 v3.101.0
github.com/jfjallid/mstypes v0.0.2
github.com/jfjallid/ndr v0.1.1
golang.org/x/net v0.50.0
+4 -4
View File
@@ -8,12 +8,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jfjallid/gofork v1.7.6 h1:OYyS2HH597860gkDxxjNsl+NZRxoAnuRI6ZsP++kYKE=
github.com/jfjallid/gofork v1.7.6/go.mod h1:r1EH4W9KY5iqtiGhAupnbzMRONsLDApdJ9EZH5NWFSc=
github.com/jfjallid/gokrb5/v8 v8.6.3 h1:fXw7wWPWoBWz5i51/xnwcwQFcKfXP1nKhV1rOR61+oM=
github.com/jfjallid/gokrb5/v8 v8.6.3/go.mod h1:t4yk6+7zUFl9ImlL8lSOY1tjhOBh1vs8zSFpV7imJyc=
github.com/jfjallid/gokrb5/v9 v9.0.0 h1:G7jtD9JHApKbWGJLCPAY30ubbfpYyN1uK9QBAx7RrLg=
github.com/jfjallid/gokrb5/v9 v9.0.0/go.mod h1:2DeagoxFvbHs/K9LUeYW65IFUIXFe+EULWqK0rRPRLw=
github.com/jfjallid/golog v0.3.6 h1:jkIyKxahU8I9fLlJPtwnOsT1ccnAcQns70iVJe/T4zs=
github.com/jfjallid/golog v0.3.6/go.mod h1:19Q/zg5OgPPd0xhFllokPnMzthzhFPZmiAGAokE7k58=
github.com/jfjallid/ldap/v3 v3.100.0 h1:PzY+1+wgQCx4Jnju766T/ns/yJXGESBhg/sHHVWSDlE=
github.com/jfjallid/ldap/v3 v3.100.0/go.mod h1:MWaFwjSodWiby7OKpedZARoUAd451tw20af79Rhm2Ag=
github.com/jfjallid/ldap/v3 v3.101.0 h1:TPWwQoo6E4htLSBH/gvHV612FRyC6pUG7RkFXSyNIqs=
github.com/jfjallid/ldap/v3 v3.101.0/go.mod h1:PvgDCEl31W1A71u5CPuNaNpmPgoiFmakA9pv0Ma+Mnc=
github.com/jfjallid/mstypes v0.0.2 h1:bYXHnkT7qnfdgc3wSKnPKcRo3jrjY+FV/HqwaYbi4zY=
github.com/jfjallid/mstypes v0.0.2/go.mod h1:a3RS3XrUS/+1FbmUNDHB2cJ878Z//brwfxBTG9PB9/M=
github.com/jfjallid/ndr v0.1.1 h1:2Go7AitvAuQ0WPmZrMsLA3qKxVXIYu3teA/sDxuQExE=
+2 -2
View File
@@ -24,8 +24,8 @@ package krb5ssp
import (
"fmt"
"github.com/jfjallid/gokrb5/v8/client"
"github.com/jfjallid/gokrb5/v8/config"
"github.com/jfjallid/gokrb5/v9/client"
"github.com/jfjallid/gokrb5/v9/config"
)
func getClientFromCachedTicket(cfg *config.Config, username, domain, spn string, spnAliases map[string][]string, settings ...func(*client.Settings)) (c *client.Client, fallbackSPN string, err error) {
+3 -3
View File
@@ -25,9 +25,9 @@ import (
"os"
"strings"
"github.com/jfjallid/gokrb5/v8/client"
"github.com/jfjallid/gokrb5/v8/config"
"github.com/jfjallid/gokrb5/v8/credentials"
"github.com/jfjallid/gokrb5/v9/client"
"github.com/jfjallid/gokrb5/v9/config"
"github.com/jfjallid/gokrb5/v9/credentials"
)
func getClientFromCachedTicket(cfg *config.Config, username, domain, spn string, spnAliases map[string][]string, settings ...func(*client.Settings)) (c *client.Client, fallbackSPN string, err error) {
+2 -2
View File
@@ -24,8 +24,8 @@ package krb5ssp
import (
"fmt"
"github.com/jfjallid/gokrb5/v8/client"
"github.com/jfjallid/gokrb5/v8/config"
"github.com/jfjallid/gokrb5/v9/client"
"github.com/jfjallid/gokrb5/v9/config"
)
func getClientFromCachedTicket(cfg *config.Config, username, domain, spn string, spnAliases map[string][]string, settings ...func(*client.Settings)) (c *client.Client, fallbackSPN string, err error) {
+11 -11
View File
@@ -33,17 +33,17 @@ import (
"github.com/jfjallid/gofork/encoding/asn1"
"github.com/jfjallid/go-smb/gss"
"github.com/jfjallid/gokrb5/v8/asn1tools"
"github.com/jfjallid/gokrb5/v8/client"
"github.com/jfjallid/gokrb5/v8/config"
"github.com/jfjallid/gokrb5/v8/crypto"
"github.com/jfjallid/gokrb5/v8/iana"
"github.com/jfjallid/gokrb5/v8/iana/asnAppTag"
"github.com/jfjallid/gokrb5/v8/iana/etypeID"
"github.com/jfjallid/gokrb5/v8/iana/keyusage"
"github.com/jfjallid/gokrb5/v8/iana/msgtype"
"github.com/jfjallid/gokrb5/v8/messages"
"github.com/jfjallid/gokrb5/v8/types"
"github.com/jfjallid/gokrb5/v9/asn1tools"
"github.com/jfjallid/gokrb5/v9/client"
"github.com/jfjallid/gokrb5/v9/config"
"github.com/jfjallid/gokrb5/v9/crypto"
"github.com/jfjallid/gokrb5/v9/iana"
"github.com/jfjallid/gokrb5/v9/iana/asnAppTag"
"github.com/jfjallid/gokrb5/v9/iana/etypeID"
"github.com/jfjallid/gokrb5/v9/iana/keyusage"
"github.com/jfjallid/gokrb5/v9/iana/msgtype"
"github.com/jfjallid/gokrb5/v9/messages"
"github.com/jfjallid/gokrb5/v9/types"
"github.com/jfjallid/golog"
"golang.org/x/net/proxy"
)
+2 -2
View File
@@ -24,8 +24,8 @@ package krb5ssp
import (
"fmt"
"github.com/jfjallid/gokrb5/v8/crypto"
"github.com/jfjallid/gokrb5/v8/types"
"github.com/jfjallid/gokrb5/v9/crypto"
"github.com/jfjallid/gokrb5/v9/types"
)
// RFC 4121 Section 4.2.6.2 Wrap Token with confidentiality, DCE-style.
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"testing"
"github.com/jfjallid/go-smb/gss"
"github.com/jfjallid/gokrb5/v8/types"
"github.com/jfjallid/gokrb5/v9/types"
)
// TestWrapDCERoundTrip verifies that WrapDCE and UnwrapDCE are inverse operations
+104
View File
@@ -0,0 +1,104 @@
// MIT License
//
// # Copyright (c) 2026 Jimmy Fjällid
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package ldap
import (
"github.com/jfjallid/go-smb/ntlmssp"
goldap "github.com/jfjallid/ldap/v3"
)
// ntlmNegotiator adapts go-smb's ntlmssp.Client to the interfaces the
// jfjallid/ldap/v3 package uses to drive an NTLM bind. It lets ldap stay free of
// any concrete NTLM (and therefore go-smb) dependency while go-smb supplies the
// implementation. It satisfies goldap.NTLMNegotiator and, optionally,
// goldap.NTLMChannelBinder and goldap.NTLMSessionProvider.
type ntlmNegotiator struct {
c *ntlmssp.Client
}
var (
_ goldap.NTLMNegotiator = (*ntlmNegotiator)(nil)
_ goldap.NTLMChannelBinder = (*ntlmNegotiator)(nil)
_ goldap.NTLMSessionProvider = (*ntlmNegotiator)(nil)
)
// Negotiate produces the NTLM NEGOTIATE_MESSAGE. The credentials (including the
// domain) are already configured on the underlying client by newNTLMNegotiator,
// so the domain argument is informational; only a non-empty workstation
// overrides the configured value.
func (n *ntlmNegotiator) Negotiate(domain, workstation string) ([]byte, error) {
if workstation != "" {
n.c.Workstation = workstation
}
return n.c.Negotiate()
}
// ChallengeResponse consumes the server CHALLENGE_MESSAGE and returns the NTLM
// AUTHENTICATE_MESSAGE.
func (n *ntlmNegotiator) ChallengeResponse(challenge []byte) ([]byte, error) {
return n.c.Authenticate(challenge)
}
// SetChannelBindingHash forwards the RFC 5929 channel-binding hash to the NTLM
// client (EPA).
func (n *ntlmNegotiator) SetChannelBindingHash(hash [16]byte) {
n.c.SetChannelBindingHash(hash)
}
// SecuritySession returns the negotiated NTLM SASL sign/seal session, or nil if
// none was established. *ntlmssp.Session satisfies goldap.SASLSession via its
// Seal/Unseal methods.
func (n *ntlmNegotiator) SecuritySession() goldap.SASLSession {
s := n.c.Session()
if s == nil {
return nil
}
return s
}
// newNTLMNegotiator builds an ntlmssp-backed negotiator from the credentials
// resolved by the ldap layer. It is registered as goldap.NTLMNegotiatorFactory
// so credential-based NTLM binds and signing detection work out of the box.
func newNTLMNegotiator(creds goldap.NTLMCredentials) (goldap.NTLMNegotiator, error) {
c := &ntlmssp.Client{
User: creds.Username,
Domain: creds.Domain,
Workstation: creds.Workstation,
Password: creds.Password,
Hash: creds.Hash,
}
// When the caller does not intend to negotiate a SASL sign/seal layer,
// strip the integrity/confidentiality flags and suppress the MIC. AD's
// strict MIC validation can otherwise reject an unsigned bind.
if !creds.SignSeal {
c.StripFlags = ntlmssp.FlgNegSign | ntlmssp.FlgNegSeal
c.DisableMIC = true
}
return &ntlmNegotiator{c: c}, nil
}
func init() {
// Provide ldap with go-smb's NTLM implementation. Importing this package is
// enough to enable credential-based NTLM binds through jfjallid/ldap/v3.
goldap.NTLMNegotiatorFactory = newNTLMNegotiator
}