...

Source file src/github.com/go-ldap/ldap/v3/bind.go

Documentation: github.com/go-ldap/ldap/v3

     1  package ldap
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	enchex "encoding/hex"
     7  	"errors"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"math/rand"
    11  	"strings"
    12  
    13  	"github.com/Azure/go-ntlmssp"
    14  	ber "github.com/go-asn1-ber/asn1-ber"
    15  )
    16  
    17  // SimpleBindRequest represents a username/password bind operation
    18  type SimpleBindRequest struct {
    19  	// Username is the name of the Directory object that the client wishes to bind as
    20  	Username string
    21  	// Password is the credentials to bind with
    22  	Password string
    23  	// Controls are optional controls to send with the bind request
    24  	Controls []Control
    25  	// AllowEmptyPassword sets whether the client allows binding with an empty password
    26  	// (normally used for unauthenticated bind).
    27  	AllowEmptyPassword bool
    28  }
    29  
    30  // SimpleBindResult contains the response from the server
    31  type SimpleBindResult struct {
    32  	Controls []Control
    33  }
    34  
    35  // NewSimpleBindRequest returns a bind request
    36  func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest {
    37  	return &SimpleBindRequest{
    38  		Username:           username,
    39  		Password:           password,
    40  		Controls:           controls,
    41  		AllowEmptyPassword: false,
    42  	}
    43  }
    44  
    45  func (req *SimpleBindRequest) appendTo(envelope *ber.Packet) error {
    46  	pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
    47  	pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
    48  	pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, req.Username, "User Name"))
    49  	pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, req.Password, "Password"))
    50  
    51  	envelope.AppendChild(pkt)
    52  	if len(req.Controls) > 0 {
    53  		envelope.AppendChild(encodeControls(req.Controls))
    54  	}
    55  
    56  	return nil
    57  }
    58  
    59  // SimpleBind performs the simple bind operation defined in the given request
    60  func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) {
    61  	if simpleBindRequest.Password == "" && !simpleBindRequest.AllowEmptyPassword {
    62  		return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
    63  	}
    64  
    65  	msgCtx, err := l.doRequest(simpleBindRequest)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	defer l.finishMessage(msgCtx)
    70  
    71  	packet, err := l.readPacket(msgCtx)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	result := &SimpleBindResult{
    77  		Controls: make([]Control, 0),
    78  	}
    79  
    80  	if len(packet.Children) == 3 {
    81  		for _, child := range packet.Children[2].Children {
    82  			decodedChild, decodeErr := DecodeControl(child)
    83  			if decodeErr != nil {
    84  				return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
    85  			}
    86  			result.Controls = append(result.Controls, decodedChild)
    87  		}
    88  	}
    89  
    90  	err = GetLDAPError(packet)
    91  	return result, err
    92  }
    93  
    94  // Bind performs a bind with the given username and password.
    95  //
    96  // It does not allow unauthenticated bind (i.e. empty password). Use the UnauthenticatedBind method
    97  // for that.
    98  func (l *Conn) Bind(username, password string) error {
    99  	req := &SimpleBindRequest{
   100  		Username:           username,
   101  		Password:           password,
   102  		AllowEmptyPassword: false,
   103  	}
   104  	_, err := l.SimpleBind(req)
   105  	return err
   106  }
   107  
   108  // UnauthenticatedBind performs an unauthenticated bind.
   109  //
   110  // A username may be provided for trace (e.g. logging) purpose only, but it is normally not
   111  // authenticated or otherwise validated by the LDAP server.
   112  //
   113  // See https://tools.ietf.org/html/rfc4513#section-5.1.2 .
   114  // See https://tools.ietf.org/html/rfc4513#section-6.3.1 .
   115  func (l *Conn) UnauthenticatedBind(username string) error {
   116  	req := &SimpleBindRequest{
   117  		Username:           username,
   118  		Password:           "",
   119  		AllowEmptyPassword: true,
   120  	}
   121  	_, err := l.SimpleBind(req)
   122  	return err
   123  }
   124  
   125  // DigestMD5BindRequest represents a digest-md5 bind operation
   126  type DigestMD5BindRequest struct {
   127  	Host string
   128  	// Username is the name of the Directory object that the client wishes to bind as
   129  	Username string
   130  	// Password is the credentials to bind with
   131  	Password string
   132  	// Controls are optional controls to send with the bind request
   133  	Controls []Control
   134  }
   135  
   136  func (req *DigestMD5BindRequest) appendTo(envelope *ber.Packet) error {
   137  	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
   138  	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
   139  	request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
   140  
   141  	auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
   142  	auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
   143  	request.AppendChild(auth)
   144  	envelope.AppendChild(request)
   145  	if len(req.Controls) > 0 {
   146  		envelope.AppendChild(encodeControls(req.Controls))
   147  	}
   148  	return nil
   149  }
   150  
   151  // DigestMD5BindResult contains the response from the server
   152  type DigestMD5BindResult struct {
   153  	Controls []Control
   154  }
   155  
   156  // MD5Bind performs a digest-md5 bind with the given host, username and password.
   157  func (l *Conn) MD5Bind(host, username, password string) error {
   158  	req := &DigestMD5BindRequest{
   159  		Host:     host,
   160  		Username: username,
   161  		Password: password,
   162  	}
   163  	_, err := l.DigestMD5Bind(req)
   164  	return err
   165  }
   166  
   167  // DigestMD5Bind performs the digest-md5 bind operation defined in the given request
   168  func (l *Conn) DigestMD5Bind(digestMD5BindRequest *DigestMD5BindRequest) (*DigestMD5BindResult, error) {
   169  	if digestMD5BindRequest.Password == "" {
   170  		return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
   171  	}
   172  
   173  	msgCtx, err := l.doRequest(digestMD5BindRequest)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	defer l.finishMessage(msgCtx)
   178  
   179  	packet, err := l.readPacket(msgCtx)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
   184  	if l.Debug {
   185  		if err = addLDAPDescriptions(packet); err != nil {
   186  			return nil, err
   187  		}
   188  		ber.PrintPacket(packet)
   189  	}
   190  
   191  	result := &DigestMD5BindResult{
   192  		Controls: make([]Control, 0),
   193  	}
   194  	var params map[string]string
   195  	if len(packet.Children) == 2 {
   196  		if len(packet.Children[1].Children) == 4 {
   197  			child := packet.Children[1].Children[0]
   198  			if child.Tag != ber.TagEnumerated {
   199  				return result, GetLDAPError(packet)
   200  			}
   201  			if child.Value.(int64) != 14 {
   202  				return result, GetLDAPError(packet)
   203  			}
   204  			child = packet.Children[1].Children[3]
   205  			if child.Tag != ber.TagObjectDescriptor {
   206  				return result, GetLDAPError(packet)
   207  			}
   208  			if child.Data == nil {
   209  				return result, GetLDAPError(packet)
   210  			}
   211  			data, _ := ioutil.ReadAll(child.Data)
   212  			params, err = parseParams(string(data))
   213  			if err != nil {
   214  				return result, fmt.Errorf("parsing digest-challenge: %s", err)
   215  			}
   216  		}
   217  	}
   218  
   219  	if params != nil {
   220  		resp := computeResponse(
   221  			params,
   222  			"ldap/"+strings.ToLower(digestMD5BindRequest.Host),
   223  			digestMD5BindRequest.Username,
   224  			digestMD5BindRequest.Password,
   225  		)
   226  		packet = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
   227  		packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
   228  
   229  		request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
   230  		request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
   231  		request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
   232  
   233  		auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
   234  		auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "DIGEST-MD5", "SASL Mech"))
   235  		auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, resp, "Credentials"))
   236  		request.AppendChild(auth)
   237  		packet.AppendChild(request)
   238  		msgCtx, err = l.sendMessage(packet)
   239  		if err != nil {
   240  			return nil, fmt.Errorf("send message: %s", err)
   241  		}
   242  		defer l.finishMessage(msgCtx)
   243  		packetResponse, ok := <-msgCtx.responses
   244  		if !ok {
   245  			return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
   246  		}
   247  		packet, err = packetResponse.ReadPacket()
   248  		l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
   249  		if err != nil {
   250  			return nil, fmt.Errorf("read packet: %s", err)
   251  		}
   252  	}
   253  
   254  	err = GetLDAPError(packet)
   255  	return result, err
   256  }
   257  
   258  func parseParams(str string) (map[string]string, error) {
   259  	m := make(map[string]string)
   260  	var key, value string
   261  	var state int
   262  	for i := 0; i <= len(str); i++ {
   263  		switch state {
   264  		case 0: // reading key
   265  			if i == len(str) {
   266  				return nil, fmt.Errorf("syntax error on %d", i)
   267  			}
   268  			if str[i] != '=' {
   269  				key += string(str[i])
   270  				continue
   271  			}
   272  			state = 1
   273  		case 1: // reading value
   274  			if i == len(str) {
   275  				m[key] = value
   276  				break
   277  			}
   278  			switch str[i] {
   279  			case ',':
   280  				m[key] = value
   281  				state = 0
   282  				key = ""
   283  				value = ""
   284  			case '"':
   285  				if value != "" {
   286  					return nil, fmt.Errorf("syntax error on %d", i)
   287  				}
   288  				state = 2
   289  			default:
   290  				value += string(str[i])
   291  			}
   292  		case 2: // inside quotes
   293  			if i == len(str) {
   294  				return nil, fmt.Errorf("syntax error on %d", i)
   295  			}
   296  			if str[i] != '"' {
   297  				value += string(str[i])
   298  			} else {
   299  				state = 1
   300  			}
   301  		}
   302  	}
   303  	return m, nil
   304  }
   305  
   306  func computeResponse(params map[string]string, uri, username, password string) string {
   307  	nc := "00000001"
   308  	qop := "auth"
   309  	cnonce := enchex.EncodeToString(randomBytes(16))
   310  	x := username + ":" + params["realm"] + ":" + password
   311  	y := md5Hash([]byte(x))
   312  
   313  	a1 := bytes.NewBuffer(y)
   314  	a1.WriteString(":" + params["nonce"] + ":" + cnonce)
   315  	if len(params["authzid"]) > 0 {
   316  		a1.WriteString(":" + params["authzid"])
   317  	}
   318  	a2 := bytes.NewBuffer([]byte("AUTHENTICATE"))
   319  	a2.WriteString(":" + uri)
   320  	ha1 := enchex.EncodeToString(md5Hash(a1.Bytes()))
   321  	ha2 := enchex.EncodeToString(md5Hash(a2.Bytes()))
   322  
   323  	kd := ha1
   324  	kd += ":" + params["nonce"]
   325  	kd += ":" + nc
   326  	kd += ":" + cnonce
   327  	kd += ":" + qop
   328  	kd += ":" + ha2
   329  	resp := enchex.EncodeToString(md5Hash([]byte(kd)))
   330  	return fmt.Sprintf(
   331  		`username="%s",realm="%s",nonce="%s",cnonce="%s",nc=00000001,qop=%s,digest-uri="%s",response=%s`,
   332  		username,
   333  		params["realm"],
   334  		params["nonce"],
   335  		cnonce,
   336  		qop,
   337  		uri,
   338  		resp,
   339  	)
   340  }
   341  
   342  func md5Hash(b []byte) []byte {
   343  	hasher := md5.New()
   344  	hasher.Write(b)
   345  	return hasher.Sum(nil)
   346  }
   347  
   348  func randomBytes(len int) []byte {
   349  	b := make([]byte, len)
   350  	for i := 0; i < len; i++ {
   351  		b[i] = byte(rand.Intn(256))
   352  	}
   353  	return b
   354  }
   355  
   356  var externalBindRequest = requestFunc(func(envelope *ber.Packet) error {
   357  	pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
   358  	pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
   359  	pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
   360  
   361  	saslAuth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
   362  	saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "EXTERNAL", "SASL Mech"))
   363  	saslAuth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "SASL Cred"))
   364  
   365  	pkt.AppendChild(saslAuth)
   366  
   367  	envelope.AppendChild(pkt)
   368  
   369  	return nil
   370  })
   371  
   372  // ExternalBind performs SASL/EXTERNAL authentication.
   373  //
   374  // Use ldap.DialURL("ldapi://") to connect to the Unix socket before ExternalBind.
   375  //
   376  // See https://tools.ietf.org/html/rfc4422#appendix-A
   377  func (l *Conn) ExternalBind() error {
   378  	msgCtx, err := l.doRequest(externalBindRequest)
   379  	if err != nil {
   380  		return err
   381  	}
   382  	defer l.finishMessage(msgCtx)
   383  
   384  	packet, err := l.readPacket(msgCtx)
   385  	if err != nil {
   386  		return err
   387  	}
   388  
   389  	return GetLDAPError(packet)
   390  }
   391  
   392  // NTLMBind performs an NTLMSSP bind leveraging https://github.com/Azure/go-ntlmssp
   393  
   394  // NTLMBindRequest represents an NTLMSSP bind operation
   395  type NTLMBindRequest struct {
   396  	// Domain is the AD Domain to authenticate too. If not specified, it will be grabbed from the NTLMSSP Challenge
   397  	Domain string
   398  	// Username is the name of the Directory object that the client wishes to bind as
   399  	Username string
   400  	// Password is the credentials to bind with
   401  	Password string
   402  	// AllowEmptyPassword sets whether the client allows binding with an empty password
   403  	// (normally used for unauthenticated bind).
   404  	AllowEmptyPassword bool
   405  	// Hash is the hex NTLM hash to bind with. Password or hash must be provided
   406  	Hash string
   407  	// Controls are optional controls to send with the bind request
   408  	Controls []Control
   409  }
   410  
   411  func (req *NTLMBindRequest) appendTo(envelope *ber.Packet) error {
   412  	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
   413  	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
   414  	request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
   415  
   416  	// generate an NTLMSSP Negotiation message for the  specified domain (it can be blank)
   417  	negMessage, err := ntlmssp.NewNegotiateMessage(req.Domain, "")
   418  	if err != nil {
   419  		return fmt.Errorf("err creating negmessage: %s", err)
   420  	}
   421  
   422  	// append the generated NTLMSSP message as a TagEnumerated BER value
   423  	auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEnumerated, negMessage, "authentication")
   424  	request.AppendChild(auth)
   425  	envelope.AppendChild(request)
   426  	if len(req.Controls) > 0 {
   427  		envelope.AppendChild(encodeControls(req.Controls))
   428  	}
   429  	return nil
   430  }
   431  
   432  // NTLMBindResult contains the response from the server
   433  type NTLMBindResult struct {
   434  	Controls []Control
   435  }
   436  
   437  // NTLMBind performs an NTLMSSP Bind with the given domain, username and password
   438  func (l *Conn) NTLMBind(domain, username, password string) error {
   439  	req := &NTLMBindRequest{
   440  		Domain:   domain,
   441  		Username: username,
   442  		Password: password,
   443  	}
   444  	_, err := l.NTLMChallengeBind(req)
   445  	return err
   446  }
   447  
   448  // NTLMUnauthenticatedBind performs an bind with an empty password.
   449  //
   450  // A username is required. The anonymous bind is not (yet) supported by the go-ntlmssp library (https://github.com/Azure/go-ntlmssp/blob/819c794454d067543bc61d29f61fef4b3c3df62c/authenticate_message.go#L87)
   451  //
   452  // See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4 part 3.2.5.1.2
   453  func (l *Conn) NTLMUnauthenticatedBind(domain, username string) error {
   454  	req := &NTLMBindRequest{
   455  		Domain:             domain,
   456  		Username:           username,
   457  		Password:           "",
   458  		AllowEmptyPassword: true,
   459  	}
   460  	_, err := l.NTLMChallengeBind(req)
   461  	return err
   462  }
   463  
   464  // NTLMBindWithHash performs an NTLM Bind with an NTLM hash instead of plaintext password (pass-the-hash)
   465  func (l *Conn) NTLMBindWithHash(domain, username, hash string) error {
   466  	req := &NTLMBindRequest{
   467  		Domain:   domain,
   468  		Username: username,
   469  		Hash:     hash,
   470  	}
   471  	_, err := l.NTLMChallengeBind(req)
   472  	return err
   473  }
   474  
   475  // NTLMChallengeBind performs the NTLMSSP bind operation defined in the given request
   476  func (l *Conn) NTLMChallengeBind(ntlmBindRequest *NTLMBindRequest) (*NTLMBindResult, error) {
   477  	if !ntlmBindRequest.AllowEmptyPassword && ntlmBindRequest.Password == "" && ntlmBindRequest.Hash == "" {
   478  		return nil, NewError(ErrorEmptyPassword, errors.New("ldap: empty password not allowed by the client"))
   479  	}
   480  
   481  	msgCtx, err := l.doRequest(ntlmBindRequest)
   482  	if err != nil {
   483  		return nil, err
   484  	}
   485  	defer l.finishMessage(msgCtx)
   486  	packet, err := l.readPacket(msgCtx)
   487  	if err != nil {
   488  		return nil, err
   489  	}
   490  	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
   491  	if l.Debug {
   492  		if err = addLDAPDescriptions(packet); err != nil {
   493  			return nil, err
   494  		}
   495  		ber.PrintPacket(packet)
   496  	}
   497  	result := &NTLMBindResult{
   498  		Controls: make([]Control, 0),
   499  	}
   500  	var ntlmsspChallenge []byte
   501  
   502  	// now find the NTLM Response Message
   503  	if len(packet.Children) == 2 {
   504  		if len(packet.Children[1].Children) == 3 {
   505  			child := packet.Children[1].Children[1]
   506  			ntlmsspChallenge = child.ByteValue
   507  			// Check to make sure we got the right message. It will always start with NTLMSSP
   508  			if len(ntlmsspChallenge) < 7 || !bytes.Equal(ntlmsspChallenge[:7], []byte("NTLMSSP")) {
   509  				return result, GetLDAPError(packet)
   510  			}
   511  			l.Debug.Printf("%d: found ntlmssp challenge", msgCtx.id)
   512  		}
   513  	}
   514  	if ntlmsspChallenge != nil {
   515  		var err error
   516  		var responseMessage []byte
   517  		// generate a response message to the challenge with the given Username/Password if password is provided
   518  		if ntlmBindRequest.Hash != "" {
   519  			responseMessage, err = ntlmssp.ProcessChallengeWithHash(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Hash)
   520  		} else if ntlmBindRequest.Password != "" || ntlmBindRequest.AllowEmptyPassword {
   521  			_, _, domainNeeded := ntlmssp.GetDomain(ntlmBindRequest.Username)
   522  			responseMessage, err = ntlmssp.ProcessChallenge(ntlmsspChallenge, ntlmBindRequest.Username, ntlmBindRequest.Password, domainNeeded)
   523  		} else {
   524  			err = fmt.Errorf("need a password or hash to generate reply")
   525  		}
   526  		if err != nil {
   527  			return result, fmt.Errorf("parsing ntlm-challenge: %s", err)
   528  		}
   529  		packet = ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
   530  		packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
   531  
   532  		request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
   533  		request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
   534  		request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
   535  
   536  		// append the challenge response message as a TagEmbeddedPDV BER value
   537  		auth := ber.Encode(ber.ClassContext, ber.TypePrimitive, ber.TagEmbeddedPDV, responseMessage, "authentication")
   538  
   539  		request.AppendChild(auth)
   540  		packet.AppendChild(request)
   541  		msgCtx, err = l.sendMessage(packet)
   542  		if err != nil {
   543  			return nil, fmt.Errorf("send message: %s", err)
   544  		}
   545  		defer l.finishMessage(msgCtx)
   546  		packetResponse, ok := <-msgCtx.responses
   547  		if !ok {
   548  			return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
   549  		}
   550  		packet, err = packetResponse.ReadPacket()
   551  		l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
   552  		if err != nil {
   553  			return nil, fmt.Errorf("read packet: %s", err)
   554  		}
   555  
   556  	}
   557  
   558  	err = GetLDAPError(packet)
   559  	return result, err
   560  }
   561  
   562  // GSSAPIClient interface is used as the client-side implementation for the
   563  // GSSAPI SASL mechanism.
   564  // Interface inspired by GSSAPIClient from golang.org/x/crypto/ssh
   565  type GSSAPIClient interface {
   566  	// InitSecContext initiates the establishment of a security context for
   567  	// GSS-API between the client and server.
   568  	// Initially the token parameter should be specified as nil.
   569  	// The routine may return a outputToken which should be transferred to
   570  	// the server, where the server will present it to AcceptSecContext.
   571  	// If no token need be sent, InitSecContext will indicate this by setting
   572  	// needContinue to false. To complete the context
   573  	// establishment, one or more reply tokens may be required from the server;
   574  	// if so, InitSecContext will return a needContinue which is true.
   575  	// In this case, InitSecContext should be called again when the
   576  	// reply token is received from the server, passing the reply token
   577  	// to InitSecContext via the token parameters.
   578  	// See RFC 4752 section 3.1.
   579  	InitSecContext(target string, token []byte) (outputToken []byte, needContinue bool, err error)
   580  	// NegotiateSaslAuth performs the last step of the Sasl handshake.
   581  	// It takes a token, which, when unwrapped, describes the servers supported
   582  	// security layers (first octet) and maximum receive buffer (remaining
   583  	// three octets).
   584  	// If the received token is unacceptable an error must be returned to abort
   585  	// the handshake.
   586  	// Outputs a signed token describing the client's selected security layer
   587  	// and receive buffer size and optionally an authorization identity.
   588  	// The returned token will be sent to the server and the handshake considered
   589  	// completed successfully and the server authenticated.
   590  	// See RFC 4752 section 3.1.
   591  	NegotiateSaslAuth(token []byte, authzid string) ([]byte, error)
   592  	// DeleteSecContext destroys any established secure context.
   593  	DeleteSecContext() error
   594  }
   595  
   596  // GSSAPIBindRequest represents a GSSAPI SASL mechanism bind request.
   597  // See rfc4752 and rfc4513 section 5.2.1.2.
   598  type GSSAPIBindRequest struct {
   599  	// Service Principal Name user for the service ticket. Eg. "ldap/<host>"
   600  	ServicePrincipalName string
   601  	// (Optional) Authorization entity
   602  	AuthZID string
   603  	// (Optional) Controls to send with the bind request
   604  	Controls []Control
   605  }
   606  
   607  // GSSAPIBind performs the GSSAPI SASL bind using the provided GSSAPI client.
   608  func (l *Conn) GSSAPIBind(client GSSAPIClient, servicePrincipal, authzid string) error {
   609  	return l.GSSAPIBindRequest(client, &GSSAPIBindRequest{
   610  		ServicePrincipalName: servicePrincipal,
   611  		AuthZID:              authzid,
   612  	})
   613  }
   614  
   615  // GSSAPIBindRequest performs the GSSAPI SASL bind using the provided GSSAPI client.
   616  func (l *Conn) GSSAPIBindRequest(client GSSAPIClient, req *GSSAPIBindRequest) error {
   617  	//nolint:errcheck
   618  	defer client.DeleteSecContext()
   619  
   620  	var err error
   621  	var reqToken []byte
   622  	var recvToken []byte
   623  	needInit := true
   624  	for {
   625  		if needInit {
   626  			// Establish secure context between client and server.
   627  			reqToken, needInit, err = client.InitSecContext(req.ServicePrincipalName, recvToken)
   628  			if err != nil {
   629  				return err
   630  			}
   631  		} else {
   632  			// Secure context is set up, perform the last step of SASL handshake.
   633  			reqToken, err = client.NegotiateSaslAuth(recvToken, req.AuthZID)
   634  			if err != nil {
   635  				return err
   636  			}
   637  		}
   638  		// Send Bind request containing the current token and extract the
   639  		// token sent by server.
   640  		recvToken, err = l.saslBindTokenExchange(req.Controls, reqToken)
   641  		if err != nil {
   642  			return err
   643  		}
   644  
   645  		if !needInit && len(recvToken) == 0 {
   646  			break
   647  		}
   648  	}
   649  
   650  	return nil
   651  }
   652  
   653  func (l *Conn) saslBindTokenExchange(reqControls []Control, reqToken []byte) ([]byte, error) {
   654  	// Construct LDAP Bind request with GSSAPI SASL mechanism.
   655  	envelope := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
   656  	envelope.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
   657  
   658  	request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
   659  	request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
   660  	request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "User Name"))
   661  
   662  	auth := ber.Encode(ber.ClassContext, ber.TypeConstructed, 3, "", "authentication")
   663  	auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "GSSAPI", "SASL Mech"))
   664  	if len(reqToken) > 0 {
   665  		auth.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(reqToken), "Credentials"))
   666  	}
   667  	request.AppendChild(auth)
   668  	envelope.AppendChild(request)
   669  	if len(reqControls) > 0 {
   670  		envelope.AppendChild(encodeControls(reqControls))
   671  	}
   672  
   673  	msgCtx, err := l.sendMessage(envelope)
   674  	if err != nil {
   675  		return nil, err
   676  	}
   677  	defer l.finishMessage(msgCtx)
   678  
   679  	packet, err := l.readPacket(msgCtx)
   680  	if err != nil {
   681  		return nil, err
   682  	}
   683  	l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
   684  	if l.Debug {
   685  		if err = addLDAPDescriptions(packet); err != nil {
   686  			return nil, err
   687  		}
   688  		ber.PrintPacket(packet)
   689  	}
   690  
   691  	// https://www.rfc-editor.org/rfc/rfc4511#section-4.1.1
   692  	// packet is an envelope
   693  	// child 0 is message id
   694  	// child 1 is protocolOp
   695  	if len(packet.Children) != 2 {
   696  		return nil, fmt.Errorf("bad bind response")
   697  	}
   698  
   699  	protocolOp := packet.Children[1]
   700  RESP:
   701  	switch protocolOp.Description {
   702  	case "Bind Response": // Bind Response
   703  		// Bind Reponse is an LDAP Response (https://www.rfc-editor.org/rfc/rfc4511#section-4.1.9)
   704  		// with an additional optional serverSaslCreds string (https://www.rfc-editor.org/rfc/rfc4511#section-4.2.2)
   705  		// child 0 is resultCode
   706  		resultCode := protocolOp.Children[0]
   707  		if resultCode.Tag != ber.TagEnumerated {
   708  			break RESP
   709  		}
   710  		switch resultCode.Value.(int64) {
   711  		case 14: // Sasl bind in progress
   712  			if len(protocolOp.Children) < 3 {
   713  				break RESP
   714  			}
   715  			referral := protocolOp.Children[3]
   716  			switch referral.Description {
   717  			case "Referral":
   718  				if referral.ClassType != ber.ClassContext || referral.Tag != ber.TagObjectDescriptor {
   719  					break RESP
   720  				}
   721  				return ioutil.ReadAll(referral.Data)
   722  			}
   723  			// Optional:
   724  			//if len(protocolOp.Children) == 4 {
   725  			//	serverSaslCreds := protocolOp.Children[4]
   726  			//}
   727  		case 0: // Success - Bind OK.
   728  			// SASL layer in effect (if any) (See https://www.rfc-editor.org/rfc/rfc4513#section-5.2.1.4)
   729  			// NOTE: SASL security layers are not supported currently.
   730  			return nil, nil
   731  		}
   732  	}
   733  
   734  	return nil, GetLDAPError(packet)
   735  }
   736  

View as plain text