// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build windows // Package ntlm provides access to the Microsoft NTLM SSP Package. // package ntlm import ( "errors" "syscall" "time" "unsafe" "github.com/alexbrainman/sspi" ) // PackageInfo contains NTLM SSP package description. var PackageInfo *sspi.PackageInfo func init() { var err error PackageInfo, err = sspi.QueryPackageInfo(sspi.NTLMSP_NAME) if err != nil { panic("failed to fetch NTLM package info: " + err.Error()) } } func acquireCredentials(creduse uint32, ai *sspi.SEC_WINNT_AUTH_IDENTITY) (*sspi.Credentials, error) { c, err := sspi.AcquireCredentials(sspi.NTLMSP_NAME, creduse, (*byte)(unsafe.Pointer(ai))) if err != nil { return nil, err } return c, nil } // AcquireCurrentUserCredentials acquires credentials of currently // logged on user. These will be used by the client to authenticate // itself to the server. It will also be used by the server // to impersonate the user. func AcquireCurrentUserCredentials() (*sspi.Credentials, error) { return acquireCredentials(sspi.SECPKG_CRED_OUTBOUND, nil) } // AcquireUserCredentials acquires credentials of user described by // domain, username and password. These will be used by the client to // authenticate itself to the server. It will also be used by the // server to impersonate the user. func AcquireUserCredentials(domain, username, password string) (*sspi.Credentials, error) { if len(domain) == 0 { return nil, errors.New("domain parameter cannot be empty") } if len(username) == 0 { return nil, errors.New("username parameter cannot be empty") } d, err := syscall.UTF16FromString(domain) if err != nil { return nil, err } u, err := syscall.UTF16FromString(username) if err != nil { return nil, err } var p []uint16 var plen uint32 if len(password) > 0 { p, err = syscall.UTF16FromString(password) if err != nil { return nil, err } plen = uint32(len(p) - 1) // do not count terminating 0 } ai := sspi.SEC_WINNT_AUTH_IDENTITY{ User: &u[0], UserLength: uint32(len(u) - 1), // do not count terminating 0 Domain: &d[0], DomainLength: uint32(len(d) - 1), // do not count terminating 0 Password: &p[0], PasswordLength: plen, Flags: sspi.SEC_WINNT_AUTH_IDENTITY_UNICODE, } return acquireCredentials(sspi.SECPKG_CRED_OUTBOUND, &ai) } // AcquireServerCredentials acquires server credentials that will // be used to authenticate client. func AcquireServerCredentials() (*sspi.Credentials, error) { return acquireCredentials(sspi.SECPKG_CRED_INBOUND, nil) } func updateContext(c *sspi.Context, dst, src []byte) (authCompleted bool, n int, err error) { var inBuf, outBuf [1]sspi.SecBuffer inBuf[0].Set(sspi.SECBUFFER_TOKEN, src) inBufs := &sspi.SecBufferDesc{ Version: sspi.SECBUFFER_VERSION, BuffersCount: 1, Buffers: &inBuf[0], } outBuf[0].Set(sspi.SECBUFFER_TOKEN, dst) outBufs := &sspi.SecBufferDesc{ Version: sspi.SECBUFFER_VERSION, BuffersCount: 1, Buffers: &outBuf[0], } ret := c.Update(nil, outBufs, inBufs) switch ret { case sspi.SEC_E_OK: // session established -> return success return true, int(outBuf[0].BufferSize), nil case sspi.SEC_I_COMPLETE_NEEDED, sspi.SEC_I_COMPLETE_AND_CONTINUE: ret = sspi.CompleteAuthToken(c.Handle, outBufs) if ret != sspi.SEC_E_OK { return false, 0, ret } case sspi.SEC_I_CONTINUE_NEEDED: default: return false, 0, ret } return false, int(outBuf[0].BufferSize), nil } // ClientContext is used by the client to manage all steps of NTLM negotiation. type ClientContext struct { sctxt *sspi.Context } // NewClientContext creates new client context. It uses client // credentials cred generated by AcquireCurrentUserCredentials or // AcquireUserCredentials and, if successful, outputs negotiate // message. Negotiate message needs to be sent to the server to // start NTLM negotiation sequence. func NewClientContext(cred *sspi.Credentials) (*ClientContext, []byte, error) { negotiate := make([]byte, PackageInfo.MaxToken) c := sspi.NewClientContext(cred, sspi.ISC_REQ_CONNECTION) authCompleted, n, err := updateContext(c, negotiate, nil) if err != nil { return nil, nil, err } if authCompleted { c.Release() return nil, nil, errors.New("ntlm authentication should not be completed yet") } if n == 0 { c.Release() return nil, nil, errors.New("ntlm token should not be empty") } negotiate = negotiate[:n] return &ClientContext{sctxt: c}, negotiate, nil } // Release free up resources associated with client context c. func (c *ClientContext) Release() error { return c.sctxt.Release() } // Expiry returns c expiry time. func (c *ClientContext) Expiry() time.Time { return c.sctxt.Expiry() } // Update completes client part of NTLM negotiation c. It uses // challenge message received from the server, and generates // authenticate message to be returned to the server. func (c *ClientContext) Update(challenge []byte) ([]byte, error) { authenticate := make([]byte, PackageInfo.MaxToken) authCompleted, n, err := updateContext(c.sctxt, authenticate, challenge) if err != nil { return nil, err } if !authCompleted { return nil, errors.New("ntlm authentication should be completed now") } if n == 0 { return nil, errors.New("ntlm token should not be empty") } authenticate = authenticate[:n] return authenticate, nil } // Sizes queries the client context for the sizes used in per-message // functions. It returns the maximum token size used in authentication // exchanges, the maximum signature size, the preferred integral size of // messages, the size of any security trailer, and any error. func (c *ClientContext) Sizes() (uint32, uint32, uint32, uint32, error) { return c.sctxt.Sizes() } // ServerContext is used by the server to manage all steps of NTLM // negotiation. Once authentication is completed the context can be // used to impersonate client. type ServerContext struct { sctxt *sspi.Context } // NewServerContext creates new server context. It uses server // credentials created by AcquireServerCredentials and client // negotiate message and, if successful, outputs challenge message. // Challenge message needs to be sent to the client to continue // NTLM negotiation sequence. func NewServerContext(cred *sspi.Credentials, negotiate []byte) (*ServerContext, []byte, error) { challenge := make([]byte, PackageInfo.MaxToken) c := sspi.NewServerContext(cred, sspi.ASC_REQ_CONNECTION) authCompleted, n, err := updateContext(c, challenge, negotiate) if err != nil { return nil, nil, err } if authCompleted { c.Release() return nil, nil, errors.New("ntlm authentication should not be completed yet") } if n == 0 { c.Release() return nil, nil, errors.New("ntlm token should not be empty") } challenge = challenge[:n] return &ServerContext{sctxt: c}, challenge, nil } // Release free up resources associated with server context c. func (c *ServerContext) Release() error { return c.sctxt.Release() } // Expiry returns c expiry time. func (c *ServerContext) Expiry() time.Time { return c.sctxt.Expiry() } // Update completes server part of NTLM negotiation c. It uses // authenticate message received from the client. func (c *ServerContext) Update(authenticate []byte) error { authCompleted, n, err := updateContext(c.sctxt, nil, authenticate) if err != nil { return err } if !authCompleted { return errors.New("ntlm authentication should be completed now") } if n != 0 { return errors.New("ntlm token should be empty now") } return nil } // ImpersonateUser changes current OS thread user. New user is // the user as specified by client credentials. func (c *ServerContext) ImpersonateUser() error { return c.sctxt.ImpersonateUser() } // RevertToSelf stops impersonation. It changes current OS thread // user to what it was before ImpersonateUser was executed. func (c *ServerContext) RevertToSelf() error { return c.sctxt.RevertToSelf() } // Sizes queries the server context for the sizes used in per-message // functions. It returns the maximum token size used in authentication // exchanges, the maximum signature size, the preferred integral size of // messages, the size of any security trailer, and any error. func (c *ServerContext) Sizes() (uint32, uint32, uint32, uint32, error) { return c.sctxt.Sizes() }