-
Notifications
You must be signed in to change notification settings - Fork 365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds GSSAPI Bind support #340
Conversation
This adds a new Mechanism for SASL Binds using GSSAPI. It does *not* implement security layers. It does *not* implement any of the newer GS2 mechanism. It does *not* implement the KERBEROSV5 mechanism. It also due to implementing GSSAPI specifically, not allow for channel bindings. Use this with caution. Closes go-ldap#115.
Thank you very much @dequbed for your work on this.
The token verification was throwing the error: _, err = token.Verify(key, keyusage.GSSAPI_ACCEPTOR_SEAL)
if err != nil {
return nil, err
} After some digging I found the GSSAPI specs about key derivation and wrapped token, the wrap token flags specs say that the flag's second bit is set if the token is sealed, my token flags value was 5 // verify if flags indicate seal (contains 0b10)
// https://datatracker.ietf.org/doc/html/rfc4121#section-4.2.2
if (token.Flags & 0b10) != 0 {
_, err = token.Verify(key, keyusage.GSSAPI_ACCEPTOR_SEAL)
if err != nil {
return nil, err
}
} With this modifications it works ! But I don't know if this is the right fix, I don't have a deep knowledge about Kerberos and GSSAPI. |
No, even if the wrap token isn't sealed (that is encrypted) KG-USAGE-ACCEPTOR-SEAL is still the correct keyusage. In fact, at this stage wrap tokens SHOULD NOT be encrypted at all since this exchange is to establish the security layer detailing exactly that. There are a few reasons this could fail, the first one coming to mind would be that despite the server indicating it has signed the token with the established subkey (bit number 3 set) it has in fact signed it with the session key. Not verifying the token also isn't a good solution here, although it only loses you mutual authentication. |
Thanks for you reply.
I am testing with:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now realize that I commented on the non-v3 code variant, but I actually tested my hacks on the v3/ package. quickly skimming, same comments probably apply to both. I tested against windows AD w/ a golang linux client built from a hacked version of this PR.
|
||
auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication") | ||
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "GSSAPI", "SASL Mech")) | ||
auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(req.token[:]), "Credentials")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only append credentials child to auth
if len(token) > 0
// Loop until we are done | ||
done := false | ||
OUTER: | ||
for !done { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for !done && err == nil {
done, err = func() (bool, error) {
// do the real work here, so that defer is executed properly
}()
}
return result, err
|
||
for _, child := range packet.Children[1].Children { | ||
if child.ClassType == ber.ClassContext && child.Tag == 7 { | ||
data, err = ioutil.ReadAll(child.Data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check for err of ReadAll
step?
return nil, err | ||
} | ||
|
||
token, err := spnego.NewKRB5TokenAPREQ(client, tkt, ekey, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf, gssapi.ContextFlagMutual}, []int{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I needed to include flags.APOptionMutualRequired (in the empty int list) for AD support
Flags: 0b100, | ||
EC: uint16(encType.GetHMACBitLength() / 8), | ||
RRC: 0, | ||
SndSeqNum: 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe properly track this in "state" instead of hardcoding the seq here
} | ||
|
||
token := &gssapi.WrapToken{} | ||
err := token.Unmarshal(input, true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
testing against AD, this only works if I hacked the Unmarshal() call, which could be done here, after the fact, instead of within Unmarshal() itself, if needed. otherwise the call to token.Verify() fails w/ a checksum mismatch.
the hack: Unmarshal() parses header, payload, checksum
in that order. to get this working with AD, I need to change the parse order: header, checksum, payload
.
I've stumbled across #115 over a year ago and was very pleased when I found this PR today. Thank you very much @dequbed. I've tried to implement it myself but didn't get it working. Based on What needs to happen to get this PR merged? @johnweldon |
🏆 |
@jankirsten can u share GSSAPIKeytabBind? |
@tooptoop4 i'm unable to supply a proper patch (corporate red tape). i left a bunch of hints above, let me know if something is unclear w/ respect to what i've suggested |
@jdef how to retrieve the username? |
@jdef @dequbed @tooptoop4 @jankirsten I'm setting up a windows AD domain service, and I need to access it via LDAP/GSSAPI/Kerberos. But I encounter this issue: I could get the service ticket from Kerberos, but the ldap bind failed. But I tried Linux kdc/openldap and that's ok. Does it seem that there is somehow DNS issue? I don't know how to set Do you have any idea where I am wrong? Thank you very much! |
@kingluo Hello When i call the function (c.GSSAPICCBind(confpath, cpath, spn)) i have an error ( LDAP Result Code 49 "Invalid Credentials": 8009030C: LdapErr: DSID-0C090579, comment: AcceptSecurityContext error, data 52e, v3839) |
Could you give some hints? Thanks. I managed to get the correct service ticket from Kerberos, but it seems that there is some problem between AD and KDC, because it happens at the first bind request between the client and AD. I search on google, but no solution was found, and somebody said it was related to the DNS setting. I am a newbie to windows AD, so do you have experience with this topic?
No, I did not have such an issue. Maybe the service ticket is invalid? |
@kingluo |
I uses a newly installed Windows Server 2022 Datacenter on the cloud and tried to install and configure AD/DNS from the server manager. The AD works well as a pure LDAP server, I could do simple bind and search to the AD. But when I turn to use Kerberos, it failed. I could get a Kerberos ticket via kinit and I use klist to confirm the TGT. |
I'm sorry I have difficulties with translation. I don't understand what your problem is yet. I have a little experience in setting up windows AD. Do you want to use the go-ldap library to access windows AD over the kerberos protocol ? We can use another means of communication telegram/email ? |
@Denis-shl Is there any setting that should be done for a Linux client to access AD/Kerberos? For example, add IP in the DNS server on the AD side, add SPN on the AD side, etc. |
@dequbed @Adphi By Bypassing the checksum verification, I could do a successful GSSAPI bind to Windows AD. @jdef From Heimdal implementation, the checksum seems to be following the payload, and I did really use Heimdal GSSAPI to access AD successfully (via bonsai or ldapsearch). So I'm confused why you said the wrap token from AD is special in the checksum/payload order. The payload should be followed by the checksum, which could be confirmed from the code: The RFC also has the same description: https://www.rfc-editor.org/rfc/rfc4121.html#section-4.2.6.2
@Denis-shl Thanks for your share. After some changes (I don't know which change is the key reason), I could use go-ldap to access AD over Kerberos. Here is my demo: https://gist.github.com/kingluo/7adbe1f27c3b952592d1aa89120a2f52 Note that I bypass the checksum verification, which should be a security risk, so I don't think it should be used in the production environment. |
I've also implemented that function as: // GSSAPI Bind using keytab
func (l *Conn) GSSAPIKeytabBind(username, realm, confpath, keytabpath, spn string) error {
config, err := k5conf.Load(confpath)
if err != nil {
return err
}
keytab, err := k5keytab.Load(keytabpath)
if err != nil {
return err
}
client := gssapi.NewWithKeytab(username, realm, keytab, config)
req := &GSSAPIBindRequest{
SPN: spn,
client: client,
}
_, err = l.GSSAPIBind(req)
return err
} It's required to add An example of use is: err = l.GSSAPIKeytabBind("host/host.example.com", "EXAMPLE.COM", "/etc/krb5.conf", "/etc/krb5.keytab", "ldap/ldap.example.com") |
@kingluo could you let me know how you addressed this issue? |
gokrb5 has no problem, IMO, but lacks negotiating flow implementation, which is to be done in go-ldap. No special setting is needed in setting up the Windows ADDS, and no DNS. Everything is step-by-step by the wizard. |
@stephen-jiadawang |
New implementation with latest |
PR can be closed |
Adds the "GSSAPI" SASL Mechanism as bind option.
It does not add gss security layers and does not implement channel binding.
To use GSSAPI you call the GSSAPICCBind like so:
On most *nix systems the krb5.conf is in
/etc/krb5.conf
and the ccache location can be found either via looking at the output ofklist
and is also usually stored in the environment variable$KRB5CCNAME
.Service Principal Names (SPN) of LDAP servers are usually of the form
ldap/<hostname>
so e.g."ldap/example.com"
.Closes #115.