...

Source file src/github.com/go-ldap/ldap/v3/gssapi/sspi.go

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

     1  //go:build windows
     2  // +build windows
     3  
     4  package gssapi
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/binary"
     9  	"fmt"
    10  
    11  	"github.com/alexbrainman/sspi"
    12  	"github.com/alexbrainman/sspi/kerberos"
    13  )
    14  
    15  // SSPIClient implements ldap.GSSAPIClient interface.
    16  // Depends on secur32.dll.
    17  type SSPIClient struct {
    18  	creds *sspi.Credentials
    19  	ctx   *kerberos.ClientContext
    20  }
    21  
    22  // NewSSPIClient returns a client with credentials of the current user.
    23  func NewSSPIClient() (*SSPIClient, error) {
    24  	creds, err := kerberos.AcquireCurrentUserCredentials()
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	return NewSSPIClientWithCredentials(creds), nil
    30  }
    31  
    32  // NewSSPIClientWithCredentials returns a client with the provided credentials.
    33  func NewSSPIClientWithCredentials(creds *sspi.Credentials) *SSPIClient {
    34  	return &SSPIClient{
    35  		creds: creds,
    36  	}
    37  }
    38  
    39  // NewSSPIClientWithUserCredentials returns a client using the provided user's
    40  // credentials.
    41  func NewSSPIClientWithUserCredentials(domain, username, password string) (*SSPIClient, error) {
    42  	creds, err := kerberos.AcquireUserCredentials(domain, username, password)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	return &SSPIClient{
    48  		creds: creds,
    49  	}, nil
    50  }
    51  
    52  // Close deletes any established secure context and closes the client.
    53  func (c *SSPIClient) Close() error {
    54  	err1 := c.DeleteSecContext()
    55  	err2 := c.creds.Release()
    56  	if err1 != nil {
    57  		return err1
    58  	}
    59  	if err2 != nil {
    60  		return err2
    61  	}
    62  	return nil
    63  }
    64  
    65  // DeleteSecContext destroys any established secure context.
    66  func (c *SSPIClient) DeleteSecContext() error {
    67  	return c.ctx.Release()
    68  }
    69  
    70  // InitSecContext initiates the establishment of a security context for
    71  // GSS-API between the client and server.
    72  // See RFC 4752 section 3.1.
    73  func (c *SSPIClient) InitSecContext(target string, token []byte) ([]byte, bool, error) {
    74  	sspiFlags := uint32(sspi.ISC_REQ_INTEGRITY | sspi.ISC_REQ_CONFIDENTIALITY | sspi.ISC_REQ_MUTUAL_AUTH)
    75  
    76  	switch token {
    77  	case nil:
    78  		ctx, completed, output, err := kerberos.NewClientContextWithFlags(c.creds, target, sspiFlags)
    79  		if err != nil {
    80  			return nil, false, err
    81  		}
    82  		c.ctx = ctx
    83  
    84  		return output, !completed, nil
    85  	default:
    86  
    87  		completed, output, err := c.ctx.Update(token)
    88  		if err != nil {
    89  			return nil, false, err
    90  		}
    91  		if err := c.ctx.VerifyFlags(); err != nil {
    92  			return nil, false, fmt.Errorf("error verifying flags: %v", err)
    93  		}
    94  		return output, !completed, nil
    95  
    96  	}
    97  }
    98  
    99  // NegotiateSaslAuth performs the last step of the SASL handshake.
   100  // See RFC 4752 section 3.1.
   101  func (c *SSPIClient) NegotiateSaslAuth(token []byte, authzid string) ([]byte, error) {
   102  	// Using SSPI rather than of GSSAPI, relevant documentation of differences here:
   103  	// https://learn.microsoft.com/en-us/windows/win32/secauthn/sspi-kerberos-interoperability-with-gssapi
   104  
   105  	// KERB_WRAP_NO_ENCRYPT (SECQOP_WRAP_NO_ENCRYPT) flag indicates Wrap and Unwrap
   106  	// should only sign & verify (not encrypt & decrypt).
   107  	const KERB_WRAP_NO_ENCRYPT = 0x80000001
   108  
   109  	// https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-decryptmessage
   110  	flags, inputPayload, err := c.ctx.DecryptMessage(token, 0)
   111  	if err != nil {
   112  		return nil, fmt.Errorf("error decrypting message: %w", err)
   113  	}
   114  	if flags&KERB_WRAP_NO_ENCRYPT == 0 {
   115  		// Encrypted message, this is unexpected.
   116  		return nil, fmt.Errorf("message encrypted")
   117  	}
   118  
   119  	// `bytes` describes available security context:
   120  	// 		"the first octet of resulting cleartext as a
   121  	// 		bit-mask specifying the security layers supported by the server and
   122  	// 		the second through fourth octets as the maximum size output_message
   123  	// 		the server is able to receive (in network byte order).  If the
   124  	// 		resulting cleartext is not 4 octets long, the client fails the
   125  	// 		negotiation.  The client verifies that the server maximum buffer is 0
   126  	// 		if the server does not advertise support for any security layer."
   127  	// From https://www.rfc-editor.org/rfc/rfc4752#section-3.1
   128  	if len(inputPayload) != 4 {
   129  		return nil, fmt.Errorf("bad server token")
   130  	}
   131  	if inputPayload[0] == 0x0 && !bytes.Equal(inputPayload, []byte{0x0, 0x0, 0x0, 0x0}) {
   132  		return nil, fmt.Errorf("bad server token")
   133  	}
   134  
   135  	// Security layers https://www.rfc-editor.org/rfc/rfc4422#section-3.7
   136  	// https://www.rfc-editor.org/rfc/rfc4752#section-3.3
   137  	// supportNoSecurity := input[0] & 0b00000001
   138  	// supportIntegrity := input[0] & 0b00000010
   139  	// supportPrivacy := input[0] & 0b00000100
   140  	selectedSec := 0 // Disabled
   141  	var maxSecMsgSize uint32
   142  	if selectedSec != 0 {
   143  		maxSecMsgSize, _, _, _, err = c.ctx.Sizes()
   144  		if err != nil {
   145  			return nil, fmt.Errorf("error getting security context max message size: %w", err)
   146  		}
   147  	}
   148  
   149  	// https://learn.microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-encryptmessage
   150  	inputPayload, err = c.ctx.EncryptMessage(handshakePayload(byte(selectedSec), maxSecMsgSize, []byte(authzid)), KERB_WRAP_NO_ENCRYPT, 0)
   151  	if err != nil {
   152  		return nil, fmt.Errorf("error encrypting message: %w", err)
   153  	}
   154  
   155  	return inputPayload, nil
   156  }
   157  
   158  func handshakePayload(secLayer byte, maxSize uint32, authzid []byte) []byte {
   159  	// construct payload and send unencrypted:
   160  	// 		"The client then constructs data, with the first octet containing the
   161  	// 		bit-mask specifying the selected security layer, the second through
   162  	// 		fourth octets containing in network byte order the maximum size
   163  	// 		output_message the client is able to receive (which MUST be 0 if the
   164  	// 		client does not support any security layer), and the remaining octets
   165  	// 		containing the UTF-8 [UTF8] encoded authorization identity.
   166  	// 		(Implementation note: The authorization identity is not terminated
   167  	// 		with the zero-valued (%x00) octet (e.g., the UTF-8 encoding of the
   168  	// 		NUL (U+0000) character)).  The client passes the data to GSS_Wrap
   169  	// 		with conf_flag set to FALSE and responds with the generated
   170  	// 		output_message.  The client can then consider the server
   171  	// 		authenticated."
   172  	// From https://www.rfc-editor.org/rfc/rfc4752#section-3.1
   173  
   174  	// Client picks security layer to use, 0 is disabled.
   175  	var selectedSecurity byte = secLayer
   176  	var truncatedSize uint32 // must be 0 if secLayer is 0
   177  	if selectedSecurity != 0 {
   178  		// Only 3 bytes to describe the max size, set the maximum.
   179  		truncatedSize = 0b00000000_11111111_11111111_11111111
   180  		if truncatedSize > maxSize {
   181  			truncatedSize = maxSize
   182  		}
   183  	}
   184  
   185  	payload := make([]byte, 4, 4+len(authzid))
   186  	binary.BigEndian.PutUint32(payload, truncatedSize)
   187  	payload[0] = selectedSecurity // Overwrites most significant byte of `maxSize`
   188  	payload = append(payload, []byte(authzid)...)
   189  
   190  	return payload
   191  }
   192  

View as plain text