Skip to content
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

Unable to connect when LDAP Channel Binding is enabled #339

Open
sukeerthiadiga opened this issue Dec 17, 2019 · 15 comments
Open

Unable to connect when LDAP Channel Binding is enabled #339

sukeerthiadiga opened this issue Dec 17, 2019 · 15 comments

Comments

@sukeerthiadiga
Copy link

Trying to connect to AD by enforcing the LDAP Channel Binding ()

Ending up with the below error

=> #<Net::LDAP::PDU:0x0000000013568ea8
  @app_tag=1,
  @ldap_controls=[],
  @ldap_result=
      {:resultCode=>49,
       :matchedDN=>"",
       :errorMessage=>"80090346: LdapErr: DSID-0C09056D, comment: AcceptSecurityContext error, data 80090346, v2580\u0000"},
  @message_id=1>
@artmotion
Copy link

Hi!
I just stumbled upon this. Any update or progress on this topic :)

@smlsml
Copy link

smlsml commented Jan 21, 2020

We've been trying to make this work as well. We use GSS-SPNEGO and code that looks like this:

    def sasl_gss_spnego(user, password, domain=nil)
      raise Net::LDAP::Error, "Invalid binding information" unless user && password

      challenge_response = proc do |challenge|
        challenge.force_encoding(Encoding::BINARY)
        t2_msg               = Net::NTLM::Message.parse(challenge)
        auth_params          = {:user => user, :password => password}
        auth_params[:domain] = domain unless domain.blank?
        t3_msg               = t2_msg.response(auth_params, {:ntlmv2 => true})
        t3_msg.user.force_encoding(Encoding::BINARY)
        t3_msg.serialize
      end

      {
        :mechanism          => "GSS-SPNEGO",
        :initial_credential => Net::NTLM::Message::Type1.new.serialize,
        :challenge_response => challenge_response,
        :method             => :sasl
      }
    end

Using the default Type1 flags (Net::NTLM::Message::Type1.new), it does not work. If I manipulate the flags, I can get it to be successful when I ask for sign and seal, but when watching it on wireshark, I don't see how it is any different than the default flags.

I have yet to require channel binding and test it however:
https://support.microsoft.com/en-us/help/4034879/how-to-add-the-ldapenforcechannelbinding-registry-entry

@fwininger
Copy link
Contributor

I will also try your code.

Microsoft enforce channel binding or ldaps with the KB of march 2020.

https://support.microsoft.com/en-us/help/4520412/2020-ldap-channel-binding-and-ldap-signing-requirement-for-windows

@fwininger
Copy link
Contributor

I have the same issue AcceptSecurityContext error 😢

@fwininger
Copy link
Contributor

According the documentation here : https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes/#rc-invalidCredentials

invalidCredentials (49)
Applicable operation types: bind
The invalidCredentials result code indicates that the client attempted to bind with a set of credentials that cannot be used to authenticate. Some of the potential reasons that this result code might be returned are:

  • The bind request targeted a user that does not exist.
  • The client tried to authenticate with an incorrect password.
  • The client tried to authenticate with a SASL bind request that included non-password credentials that could not be successfully verified.
  • The bind request targeted a user that is not permitted to authenticate for some reason (for example, because the account has been locked, the user’s password has expired, etc.).

@fwininger
Copy link
Contributor

I have done the implementation of the net-ldap-gss-spnego gem.

It's work for me with default AD settings.

https://github.com/fwininger/ruby-net-ldap-gss-spnego

@raj-sharan
Copy link

To handle the channel binding you can use Net::NTLM::Client, it creates type3 message using challenge(type2) and channel binding.
For creating channel binding you need server certificate that can be fetched from the type1 response or @connection

ntlm_client = Net::NTLM::Client.new(user, pass, {domain: "XYZ"})
binding = @connection.peer_cert.nil? ? nil : Net::NTLM::ChannelBinding.create(@connection.peer_cert)
type3 = ntlm_client.init_context(Base64.encode64(type2_challenge), binding)

@bmalets
Copy link

bmalets commented Jun 18, 2020

@raj-sharan could you please "beautify" your code example?
it's a bit hard to understand where exactly this defined type3 message needs to be added 🙂

@bmalets
Copy link

bmalets commented Jun 18, 2020

I tried ruby-net-ldap-gss-spnego gem, but it does not fully work for me:

If on LDAP server-side, configuration flag EnforceChannelBinding is set to 1 -
authentication using "ruby-net-ldap-gss-spnego" adapter works fine.

But, according to Microsoft recommendations EnforceChannelBinding value should be changed to 2. In this case, authentication is broken: LDAP server returns "LDAP Error-code: 49 (Invalid Credentials)" without more details. (username and password are 100% correct, tested multiple times with multiple users)

BTW, Is SSL/TLS config mandatory for ChannelBinding? Should it work without SSL certificates? Using Ldap::Client these encryption options etc:

encryption: {
  method: :simple_tls,
  tls_options: {verify_mode: OpenSSL::SSL::VERIFY_NONE}
}

@fwininger
Copy link
Contributor

I tried ruby-net-ldap-gss-spnego gem, but it does not fully work for me:

If on LDAP server-side, configuration flag EnforceChannelBinding is set to 1 -
authentication using "ruby-net-ldap-gss-spnego" adapter works fine.

But, according to Microsoft recommendations EnforceChannelBinding value should be changed to 2. In this case, authentication is broken: LDAP server returns "LDAP Error-code: 49 (Invalid Credentials)" without more details. (username and password are 100% correct, tested multiple times with multiple users)

Thanks for this comment, I don't have time this week to fix the ruby-net-ldap-gss-spnego but I open for any PR to solve this issue.

@smlsml
Copy link

smlsml commented Jun 18, 2020 via email

@bmalets
Copy link

bmalets commented Jun 22, 2020

As Channel binding is an SSL/TLS concept only - I added SSL/TLS settings to my LDAP configurations.
Unfortunately, I am not "MS guru" to understand and even get more verbose logs from LDAP server side :(
But here is a more detailed example when signing and binding don't want to work together:

require 'net/ldap'
require 'net/ldap/auth_adapter/gss_spnego'

ldap_options = {
  hosts:  [
    ['ldap_hostname.lan', 636]
  ],
  base: 'DC=ldap_hostname,DC=lan',
  encryption: {
    method: :simple_tls,
    tls_options: {
      ca_file: '/full/path/to/cert.pem',
      ssl_version: 'TLSv1_2'
    }
  },
  auth: {
    auth_method: 'GSS-SPNEGO',
    username: 'bmalets', # username, not DN
    password: 'password'
  }
}

ldap = Net::LDAP.new(ldap_options)

# When LDAP server registry settings are:
# LDAPServerIntegrity       = 2 (Always sign (Level 2))
# LdapEnforceChannelBinding = 1 (DWORD value: 1 indicates enabled, when supported. All clients that are running on a version of Windows that has been updated to support channel binding tokens (CBT) must provide channel binding information to the server. Clients that are running a version of Windows that has not been updated to support CBT do not have to do so. This is an intermediate option that allows for application compatibility.)

ldap.bind
ldap.get_operation_result # => 0. Success

# When LDAP server registry settings are:
# LDAPServerIntegrity       = 2 (Always sign (Level 2))
# LdapEnforceChannelBinding = 2 (DWORD value: 2 indicates enabled, always. All clients must provide channel binding information. The server rejects authentication requests from clients that do not do so.)

ldap.bind
ldap.get_operation_result # => Error-code: 49. Invalid Credentials

@raj-sharan
Copy link

raj-sharan commented Jun 22, 2020

@raj-sharan could you please "beautify" your code example?
it's a bit hard to understand where exactly this defined type3 message needs to be added 🙂

@bmalets, You should try this
It should work with LdapEnforceChannelBinding = 2

require 'net/ldap'
require 'net/ntlm'
require 'net/ldap/auth_adapter/gss_spnego'

#Patch bind method to share the peer_cert
module Net
  class LDAP
    class AuthAdapter
      class Sasl

        # Patch this method
        def bind(auth)
          ...........
          ...........
          n = 0
          loop do
            ..........
            ..........

            # Instead
            # cred = chall.call(pdu.result_server_sasl_creds)
            # Add following code

            cred =
              if @connection.socket.respond_to?(:peer_cert)
                chall.call(pdu.result_server_sasl_creds, @connection.socket.peer_cert)
              else
                chall.call(pdu.result_server_sasl_creds)
              end
          end

          raise Net::LDAP::SASLChallengeOverflowError, "why are we here?"
        end
      end
    end
  end
end

def sasl_gss_spnego(credential)
  challenge_response = proc do |challenge, peer_cert|
    challenge.force_encoding(Encoding::BINARY)
    ntlm_client       = Net::NTLM::Client.new(credential[:user], credential[:password], {domain: credential[:domain]})
    encoded_challenge = Base64.encode64(challenge)
    channel_binding   = peer_cert ? Net::NTLM::ChannelBinding.create(peer_cert) : nil

    t3_msg = ntlm_client.init_context(encoded_challenge, channel_binding)

    t3_msg.user.force_encoding(Encoding::BINARY)
    t3_msg.serialize
  end

  {
    :mechanism          => "GSS-SPNEGO",
    :initial_credential => Net::NTLM::Message::Type1.new.serialize,
    :challenge_response => challenge_response,
    :method             => :sasl
  }
end

ldap_options = {
  hosts:  [
            ['ldap_hostname.lan', 636]
          ],
  base: 'DC=ldap_hostname,DC=lan',
  encryption: {
    method: :simple_tls,
    tls_options: {
      ca_file: '/full/path/to/cert.pem',
      ssl_version: 'TLSv1_2'
    }
  },
  auth: sasl_gss_spnego({:user => "bmalets", :password => "password", :domain => 'DOMAIN_NAME'})
}

ldap = Net::LDAP.new(ldap_options)

ldap.bind
ldap.get_operation_result

@smlsml
Copy link

smlsml commented Jun 22, 2020 via email

@bmalets
Copy link

bmalets commented Jul 9, 2020

@smlsml, thank you for your answer.
Did not find any info in Windows Server documentation that LDAPServerIntegrity and LdapEnforceChannelBinding are mutually
exclusive... Could you please share some links about this if you have one?

I got only one page about MS update recommendations with this message:

To maximize compatibility with older operating system versions (Windows Server 2008 and earlier versions), we recommend that you enable this setting with a value of 1.

But LDAP server that I am trying to connect is MS 2012, so it definitely does not affect my case.

So, "49. Invalid Credentials" response is an expected behavior when LDAPServerIntegrity = 2 and LdapEnforceChannelBinding = 2.
And there are only two ways to make it work:

  • set LDAPServerIntegrity = 1 and LdapEnforceChannelBinding = 2
  • or set LDAPServerIntegrity = 2 and LdapEnforceChannelBinding = 1

Is it correct? 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants