mirror of
https://github.com/jfjallid/go-smb
synced 2026-06-08 15:02:50 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user