-
-
Save kingluo/7adbe1f27c3b952592d1aa89120a2f52 to your computer and use it in GitHub Desktop.
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) | |
} |
@Denis-shl No, I didn't touch any code of go-ldap. You need to use the master branch for the new GSSAPIClient interface. Refer to go.mod
file above.
@kingluo
Thank you, I managed to launch it.
I still don't fully understand why we can't use this edition when developing a product?Can you tell me which way I should carry out the resech?
@Denis-shl
Because it skips checksum verification in the last step as a workaround. And as a security protocol, I think it's better to use robust and
time-tested C library e.g. Heimdal libraries, rather than an arbitrary code snippet from the community. As far as I know, only golang reinvents the wheel (gokrb5) and even lacks of details of GSSAPI negotiation parts, while other programming languages wrap MIT/Heimdal libraries.
@kingluo
I implemented connecting and sending requests via openldap. Do you think that I can get a ready-made connection from the C libraries, and bind it to go-ldap? Or use the full functionality of openldap ?
@kingluo I implemented connecting and sending requests via openldap. Do you think that I can get a ready-made connection from the C libraries, and bind it to go-ldap? Or use the full functionality of openldap ?
@Denis-shl I don't think so. go-ldap is self-included. Do you really need to use golang? The most benefit of go-ldap I think is you don't need to depend on C libraries, which have different packages on different Linux distros. If not, then python may be a good choice: https://bonsai.readthedocs.io/, and I plan to use it too.
@kingluo
We have a great app on golang . One of the functionality of this application is account management and the release of keytabs in AD, go-ldap is a good library that covers 90% of our requirements. But it lacks kerberos support. Do you think it is possible to implement checksum verification as expected ? And if you use the code above with TLS , it still won 't be safe ?
@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
@kingluo Thank you very much.
@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.
@kingluo
./main.go:152:20: too many arguments in call to l.GSSAPIBind
have (*GSSAPIState, string, string)
want (*ldap.GSSAPIBindRequest)
tell me please what changes you made in go-ldap ?