-
Notifications
You must be signed in to change notification settings - Fork 355
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
[bug] Could not make a working code example on connecting to LDAP with Kerberos from a linux machine to a Windows Server #536
Comments
TLDR; On a freshly installed Windows Server 2019, I cannot connect using Kerberos with |
I am experiencing the same issue. The following code works fine: conn, _ = ldap.DialURL("ldap://ldap.example.com")
if err := conn.Bind(cfg.User+"@"+cfg.Domain.Name, cfg.Password); err != nil {
return err
} However, when I try to use GSSAPI, I get an error: client, _ := gssapi.NewClientFromCCache(
"/tmp/krb5cc_0",
"/etc/krb5.conf",
client.DisablePAFXFAST(true),
)
// OR
client, _ := gssapi.NewClientWithPassword(
"admin",
"MY.REALM",
"PASSWORD",
"/etc/krb5.conf",
client.DisablePAFXFAST(true),
client.AssumePreAuthentication(false),
)
if err := client.Login(); err != nil {
return err
}
conn, _ = ldap.DialURL("ldap://ldap.example.com")
err = conn.GSSAPIBind(client, "ldap/"+cfg.Service, "")
if err != nil {
return err
} This results in the following error:
I am running the code inside a container. In the container, I install this: RUN apt-get update && apt-get install -y \
bash \
dnsutils \
samba \
ldap-utils \
iputils-ping \
krb5-user \
ntp \
libsasl2-modules-gssapi-mit Additionally, I tested the connection using the following command and it worked as expected: ldapwhoami -Y GSSAPI -H ldap://ldap.example.com
SASL/GSSAPI authentication started
SASL username: [email protected]
SASL SSF: 64
SASL data security layer installed.
u:MY\admin |
I had the exact same error as @p0dalirius . I managed to get it working by following @jdef's advice in #340 , namely adding []int{flags.APOptionMutualRequired} as last parameter to spnego.NewKRB5TokenAPREQ and switching the order of checksum and payload when unmarshaling the wraptoken. You can find a working example in this gist |
Hey @rewindrepeat, Thank you for your advice, I used your patched For anyone interested I provide the full proof of concept code of this issue here: poc_ldap_gssapi_patch.tar.gz Root CauseAs stated by @jdef and @rewindrepeat, the problem can be fixed by adding the Line 113 in 601814b
The fix should be: token, err := spnego.NewKRB5TokenAPREQ(client.Client, tkt, ekey, gssapiFlags, []int{flags.APOptionMutualRequired}) More details on this in the section-5.5.1 of RFC 4120 (https://datatracker.ietf.org/doc/html/rfc4120#section-5.5.1) ):
==> Which is exactly what we want here, for a Kerberos authentication Potential Fix
Best regards, |
Glad the patch is working for you @p0dalirius A potential fix should also include a modified version of gokrb5's Unsmarshal function for WrapToken, included in the patched example client as func UnmarshalWrapToken (accompanied by required func getGssWrapTokenId) Without the modified unmarshal token.Verify() results in a checksum mismatch error, as mentioned here |
Hi @rewindrepeat thank you very much! Everything works for me too! |
…tSecContext(...), fixes go-ldap#536
…tSecContext(...), fixes go-ldap#536
…tSecContext(...), fixes go-ldap#536
…tSecContext(...), fixes go-ldap#536
Hey @rewindrepeat, @VITEK-THE-BEST, I have proposed a fix for the Kerberos authentication problem by adding an option to pass the APOptions list of flags when creating a What do you think about this? Best regards, |
Hey @p0dalirius, I think passing the APOptions flags as a parameter is the way to go, this looks good to me. But hard wiring the UnmarshalWrapToken func will most likely break existing code. According to RFC 4121, the implementation in gokrb5 should be correct and it seems like it's only ActiveDirectory that's implementing the wrap token differently? (I assume the current implementation of GSSAPI/SASL in go-ldap has been working with other LDAP servers and the problem only occurs with ActiveDirectory.) Maybe one could pass another parameter to bind.GSSAPIBindRequest, which can then be passed to client.NegotiateSaslAuth to determine if the standard unmarshal function from gokrb5 should be used or the modfied version (which could be included in the client code, as in your PR). I don't know, maybe the maintainers could chip in with how they would like to have this implemented? |
I may be reading it wrong, but RFC 4121 also mentions that Wrap token (excluding the header) may be rotated to the right by A few months ago, when dealing with the same problem, I followed @jdef's advice from #340 and manually swapped checksum and payload, which allowed me to successfully connect to Active Directory, but broke FreeIPA token verification instead. Then I noticed that Active Directory responds with I ended up replacing unmarshal method with a function that rotates token back |
Nice find
…On Wed, Nov 20, 2024, 7:07 PM olde-ducke ***@***.***> wrote:
I may be reading it wrong, but RFC 4121
<https://datatracker.ietf.org/doc/html/rfc4121#section-4.2.5> also
mentions that Wrap token (excluding the header) may be rotated to the right
by RRC octets.
A few months ago, when dealing with the same problem, I followed @jdef
<https://github.com/jdef>'s advice from #340
<#340> and manually swapped checksum
and payload, which allowed me to successfully connect to Active Directory,
but broke FreeIPA token verification instead. Then I noticed that Active
Directory responds with RRC set to 12, unlike FreeIPA (which responds
with 0), I checked the RFC 4121 and gokrb5 sources and did not find
anything described in section 4.2.5 in the unmarshal method.
I ended up replacing unmarshal method with a function that rotates token
back RRC octets (12 actually ended up being exactly the number of
rotations needed to swap payload and checksum places) before writing
checksum and payload to the struct, which worked for both Active Directory
and FreeIPA, at least in my case.
—
Reply to this email directly, view it on GitHub
<#536 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAR5KLAVE4YHGDM72XXU6X32BUP4FAVCNFSM6AAAAABRV6KEGWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIOBZG44TGOBXGM>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Thanks @olde-ducke , that's the missing puzzle piece 😃 Do you still have the code of your unmarshal method and could share it? Adapting this answer from SO I ended up with this rotateLeft function: func rotateLeft(data []byte, k int) []byte {
if k < 0 || len(data) == 0 {
return data
}
r := k % len(data)
data = append(data[r:], data[:r]...)
return data
} and this UnmarshalWrapToken func : func UnmarshalWrapToken(wt *gssapi.WrapToken, b []byte, expectFromAcceptor bool) error {
// Check if we can read a whole header
if len(b) < 16 {
return errors.New("bytes shorter than header length")
}
// Is the Token ID correct?
if !bytes.Equal(getGssWrapTokenId()[:], b[0:2]) {
return fmt.Errorf("wrong Token ID. Expected %s, was %s",
hex.EncodeToString(getGssWrapTokenId()[:]),
hex.EncodeToString(b[0:2]))
}
// Check the acceptor flag
flags := b[2]
isFromAcceptor := flags&0x01 == 1
if isFromAcceptor && !expectFromAcceptor {
return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
}
if !isFromAcceptor && expectFromAcceptor {
return errors.New("expected acceptor flag is not set: expecting a token from the acceptor, not the initiator")
}
// Check the filler byte
if b[3] != gssapi.FillerByte {
return fmt.Errorf("unexpected filler byte: expecting 0xFF, was %s ", hex.EncodeToString(b[3:4]))
}
checksumL := binary.BigEndian.Uint16(b[4:6])
// Sanity check on the checksum length
if int(checksumL) > len(b)-gssapi.HdrLen {
return fmt.Errorf("inconsistent checksum length: %d bytes to parse, checksum length is %d", len(b), checksumL)
}
rrc := binary.BigEndian.Uint16(b[6:8])
data := b[16:]
if rrc > 0 {
// data was rotated to the right during wrap, now rotate left during unwrap
data = rotateLeft(data, int(rrc))
}
payloadL := len(data) - int(checksumL)
wt.Flags = flags
wt.EC = checksumL
wt.RRC = rrc
wt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
wt.Payload = data[:payloadL]
wt.CheckSum = data[payloadL:]
return nil
} It "works", but I'm not sure if my implementation is correct or if I'm doing something stupid. Also, looking at the MIT krb5 code, it seems like they're doing some additional checks and stuff based on other values, but I fail to understand it all. But maybe simply taking the value in the RRC field and rotating by it will work in most cases. |
Hey @rewindrepeat, sorry for the late reply. My code is pretty much the same as yours, although I managed to do the rotation in-place, but now that I think about it, it is probably not a good idea to modify the original raw response. Looking at your code and MIT krb5 sources, I would probably do something like this now: func rotateLeft(data []byte, rc int) []byte {
if len(data) == 0 || rc%len(data) <= 0 {
return data
}
rc %= len(data)
return append(data[rc:], data[:rc]...)
}
func UnmarshalWrapToken(wt *gssapi.WrapToken, b []byte, expectFromAcceptor bool) error {
// original Unwrap code
rrc := binary.BigEndian.Uint16(b[6:8])
data := b[16:]
data = rotateLeft(data, int(rrc))
payloadL := len(data) - int(checksumL)
wt.Flags = flags
wt.EC = checksumL
wt.RRC = rrc
wt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
wt.Payload = data[:payloadL]
wt.CheckSum = data[payloadL:]
return nil
} But your code works just fine on my end. And yes, there's are a lot more things going on inside the MIT krb5 Unfortunately, I have no idea how to trigger this flag to be set, or who even initiates Wrap tokens with a confidentiality mechanism in the first place. There is a flag It might be a good idea to throw a loud Setting either of these two flags will result in LDAP error 49: All of the above (except the sequence number, which I think should be handled by the GSS-API client) should probably be implemented or fixed in gokrb5 itself, but it is currently inactive and the maintainer has not been around for over a year at this point.
with |
Discussed in https://github.com/orgs/go-ldap/discussions/533
Originally posted by p0dalirius October 11, 2024
Hi,
Has anyone been able to connect to a remote Windows Server LDAP service using Kerberos from a linux machine using this library? From what I understand this should be feasible, but I can't find a working example. I am trying to connect to the domain controller
SRV-DC01
of my domainLAB.local
running on Windows Server 2019, this is a default fresh installation.Initially I had a
KDC did not respond appropriately to FAST negotiation
because I did not use theclient.DisablePAFXFAST(true)
option inclient.NewWithPassword()
. Now I pass all the authentications steps up to the SASL bind on LDAP, and I get aLDAP Result Code 49 "Invalid Credentials": 80090308: LdapErr: DSID-0C09058A, comment: AcceptSecurityContext error, data 57, v4563
eventhough my credentials are valid.This is the example program to connect to LDAP using Kerberos:
At this point I am stuck with a
LDAP Result Code 49 "Invalid Credentials": 80090308: LdapErr: DSID-0C09058A, comment: AcceptSecurityContext error, data 57, v4563
error, eventhough my credentials are valid:I have a running Wireshark and I got the following packets:
My latest TGS-REP packet (number 27) is the following:
And when binding using SASL
bindRequest(1) "<ROOT>" sasl
(pkt number 32) I have:And I get a bindResponse
invalidCredentials
(pkt number 34):The credentials used are valid on the domain (I can login, and furthermore the initial Kerberos authentication do work until the
ldapConnection.GSSAPIBind()
call)If anyone have a working example or can tell me what goes wrong here I'd love that!
Thank you in advance for your help!
Best regards,
The text was updated successfully, but these errors were encountered: