ruby-net-ldap icon indicating copy to clipboard operation
ruby-net-ldap copied to clipboard

Unable to connect when LDAP Channel Binding is enabled

Open sukeerthiadiga opened this issue 5 years ago • 15 comments

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>

sukeerthiadiga avatar Dec 17 '19 13:12 sukeerthiadiga

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

artmotion avatar Jan 13 '20 10:01 artmotion

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

smlsml avatar Jan 21 '20 21:01 smlsml

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 avatar Jan 28 '20 08:01 fwininger

I have the same issue AcceptSecurityContext error :cry:

fwininger avatar Jan 28 '20 09:01 fwininger

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 avatar Jan 28 '20 09:01 fwininger

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

fwininger avatar Jan 28 '20 14:01 fwininger

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)

raj-sharan avatar Feb 04 '20 19:02 raj-sharan

@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 :slightly_smiling_face:

bmalets avatar Jun 18 '20 10:06 bmalets

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}
}

bmalets avatar Jun 18 '20 10:06 bmalets

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.

fwininger avatar Jun 18 '20 11:06 fwininger

Channel binding is an SSL/TLS concept only.

LDAP signing and sealing is a non-secure only concept too.

On Thu, Jun 18, 2020 at 3:26 AM Bohdan Malets [email protected] wrote:

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 https://support.microsoft.com/en-us/help/4034879/how-to-add-the-ldapenforcechannelbinding-registry-entry 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

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

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

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/ruby-ldap/ruby-net-ldap/issues/339#issuecomment-645929293, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACXAZGT23B2NCDTPH7QLRTRXHTTRANCNFSM4J33PZEA .

smlsml avatar Jun 18 '20 19:06 smlsml

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

bmalets avatar Jun 22 '20 10:06 bmalets

@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

raj-sharan avatar Jun 22 '20 18:06 raj-sharan

LDAPServerIntegrity and LdapEnforceChannelBinding are mutually exclusive. The reason your example didn't work is you changed LdapEnforceChannelBinding from 1 to 2. If you repeat that test with only changing LDAPServerIntegrity from 1 to 2 but leaving LdapEnforceChannelBinding at 1, it will work and prove that LDAPServerIntegrity doesn't matter when using SSL/TLS (which is what MS says is the case).

On Mon, Jun 22, 2020 at 3:52 AM Bohdan Malets [email protected] wrote:

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.bindldap.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.bindldap.get_operation_result # => Error-code: 49. Invalid Credentials

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/ruby-ldap/ruby-net-ldap/issues/339#issuecomment-647441592, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACXAZH7V2CLWTXSMJWWVYDRX4ZYLANCNFSM4J33PZEA .

smlsml avatar Jun 22 '20 18:06 smlsml

@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? :thinking:

bmalets avatar Jul 09 '20 14:07 bmalets