Skip to content

Instantly share code, notes, and snippets.

@kingluo
Last active March 9, 2023 18:01
Show Gist options
  • Save kingluo/7adbe1f27c3b952592d1aa89120a2f52 to your computer and use it in GitHub Desktop.
Save kingluo/7adbe1f27c3b952592d1aa89120a2f52 to your computer and use it in GitHub Desktop.
go-ldap GSSAPI
module github.com/kingluo/foobar
go 1.19
require (
github.com/go-ldap/ldap/v3 v3.4.5-0.20230226130424-b64a808c288f
gopkg.in/jcmturner/gokrb5.v7 v7.5.0
)
require (
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/stretchr/testify v1.8.1 // indirect
golang.org/x/crypto v0.6.0 // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
)
package main
import (
"fmt"
"log"
"gopkg.in/jcmturner/gokrb5.v7/client"
"gopkg.in/jcmturner/gokrb5.v7/config"
"gopkg.in/jcmturner/gokrb5.v7/crypto"
"gopkg.in/jcmturner/gokrb5.v7/gssapi"
"gopkg.in/jcmturner/gokrb5.v7/iana/flags"
"gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
"gopkg.in/jcmturner/gokrb5.v7/messages"
"gopkg.in/jcmturner/gokrb5.v7/spnego"
"gopkg.in/jcmturner/gokrb5.v7/types"
ldap "github.com/go-ldap/ldap/v3"
)
type GSSAPIState struct {
token spnego.KRB5Token
ekey types.EncryptionKey
Subkey types.EncryptionKey
asrep bool
}
func (state *GSSAPIState) DeleteSecContext() error {
return nil
}
func (state *GSSAPIState) InitSecContext(target string, _ []byte) (outputToken []byte, needContinue bool, err error) {
cfg, err := config.Load("/opt/bonsai/.ci/krb5/krb5.conf")
if err != nil {
log.Panic(err)
}
cl := client.NewClientWithPassword("chuck", "BONSAI.TEST", "xxx", cfg, client.DisablePAFXFAST(true))
err = cl.Login()
if err != nil {
log.Panic(err)
}
tkt, key, err := cl.GetServiceTicket("ldap/windows.bonsai.test")
if err != nil {
log.Panic(err)
}
token, err := spnego.NewKRB5TokenAPREQ(cl, tkt, key, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf, gssapi.ContextFlagMutual}, []int{flags.APOptionMutualRequired})
if err != nil {
log.Panic(err)
}
state.ekey = key
state.token = token
outputToken, err = token.Marshal()
if err != nil {
log.Panic(err)
}
needContinue = false
return
}
func (state *GSSAPIState) NegotiateSaslAuth(input []byte, authzid string) ([]byte, error) {
if !state.asrep {
err := state.token.Unmarshal(input)
if err != nil {
return nil, err
}
if state.token.IsAPRep() {
state.asrep = true
encpart, err := crypto.DecryptEncPart(state.token.APRep.EncPart, state.ekey, keyusage.AP_REP_ENCPART)
if err != nil {
return nil, err
}
part := &messages.EncAPRepPart{}
err = part.Unmarshal(encpart)
if err != nil {
return nil, err
}
state.Subkey = part.Subkey
}
if state.token.IsKRBError() {
return nil, state.token.KRBError
}
return make([]byte, 0), nil
}
token := &gssapi.WrapToken{}
err := token.Unmarshal(input, true)
if err != nil {
return nil, err
}
if (token.Flags & 0b1) == 0 {
return nil, fmt.Errorf("Got a Wrapped token that's not from the server")
}
key := state.ekey
if (token.Flags & 0b100) != 0 {
key = state.Subkey
}
if (token.Flags & 0b10) != 0 {
_, err = token.Verify(key, keyusage.GSSAPI_ACCEPTOR_SEAL)
if err != nil {
return nil, err
}
}
pl := token.Payload
if len(pl) != 4 {
return nil, fmt.Errorf("Server send bad final token for SASL GSSAPI Handshake")
}
// We never want a security layer
b := [4]byte{0, 0, 0, 0}
payload := append(b[:], []byte(authzid)...)
encType, err := crypto.GetEtype(key.KeyType)
if err != nil {
return nil, err
}
token = &gssapi.WrapToken{
Flags: 0b100,
EC: uint16(encType.GetHMACBitLength() / 8),
RRC: 0,
SndSeqNum: 1,
Payload: payload,
}
if err := token.SetCheckSum(key, keyusage.GSSAPI_INITIATOR_SEAL); err != nil {
return nil, err
}
return token.Marshal()
}
func main() {
l, err := ldap.DialURL("ldap://bonsai.test")
if err != nil {
log.Fatal(err)
}
defer l.Close()
cli := &GSSAPIState{}
err = l.GSSAPIBind(cli, "ldap/windows.bonsai.test", "")
if err != nil {
log.Fatal(err)
}
searchRequest := ldap.NewSearchRequest(
"ou=xxx,dc=bonsai,dc=test",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
"(objectclass=*)",
[]string{},
nil,
)
sr, err := l.Search(searchRequest)
if err != nil {
log.Fatal(err)
}
sr.PrettyPrint(2)
}
@kingluo
Copy link
Author

kingluo commented Mar 9, 2023

@Denis-shl
If the checksum part gets fixed, then I think it's almost safe. And yes, if over TLS and the certificate of the LDAP server gets verified at the client side, i.e. the server is trusted, then the checksum may be skipped. In fact, mutual TLS is the SASL EXTERNAL auth method.
But without TLS, it's better to use traditional C GSSAPI lib. The gokrb5 already handles KDC stuff, so we only need to handle the GSSAPI negotiation part. Luckily, this part could be done independently of the network transport. However, it takes time to develop because golang has no good wrapper here. Maybe you could refer to this code: https://github.com/greenplum-db/gssapi/blob/main/context.go

@Denis-shl
Copy link

@kingluo Thank you very much.

@kingluo
Copy link
Author

kingluo commented Mar 9, 2023

@Denis-shl Here is my lua library, which encapsulates bonsai.
https://github.com/kingluo/lua-resty-ffi-ldap

If you're interested, please have a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment