463 lines
16 KiB
Go
463 lines
16 KiB
Go
// Copyright 2016 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 negotiate provides access to the Microsoft Negotiate SSP Package.
|
|
//
|
|
package negotiate
|
|
|
|
import (
|
|
"errors"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/alexbrainman/sspi"
|
|
)
|
|
|
|
// TODO: maybe (if possible) move all winapi related out of sspi and into sspi/internal/winapi
|
|
|
|
// PackageInfo contains Negotiate SSP package description.
|
|
var PackageInfo *sspi.PackageInfo
|
|
|
|
func init() {
|
|
var err error
|
|
PackageInfo, err = sspi.QueryPackageInfo(sspi.NEGOSSP_NAME)
|
|
if err != nil {
|
|
panic("failed to fetch Negotiate package info: " + err.Error())
|
|
}
|
|
}
|
|
|
|
func acquireCredentials(principalName string, creduse uint32, ai *sspi.SEC_WINNT_AUTH_IDENTITY) (*sspi.Credentials, error) {
|
|
c, err := sspi.AcquireCredentials(principalName, sspi.NEGOSSP_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)
|
|
}
|
|
|
|
// TODO: see if I can share this common ntlm and negotiate code
|
|
|
|
// 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(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
|
|
}
|
|
p, err := syscall.UTF16FromString(password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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: uint32(len(p) - 1), // do not count terminating 0
|
|
Flags: sspi.SEC_WINNT_AUTH_IDENTITY_UNICODE,
|
|
}
|
|
return acquireCredentials("", sspi.SECPKG_CRED_OUTBOUND, &ai)
|
|
}
|
|
|
|
// AcquireServerCredentials acquires server credentials that will
|
|
// be used to authenticate clients.
|
|
// The principalName parameter is passed to the underlying call to
|
|
// the winapi AcquireCredentialsHandle function (and specifies the
|
|
// name of the principal whose credentials the underlying handle
|
|
// will reference).
|
|
// As a special case, using an empty string for the principal name
|
|
// will require the credential of the user under whose security context
|
|
// the current process is running.
|
|
func AcquireServerCredentials(principalName string) (*sspi.Credentials, error) {
|
|
return acquireCredentials(principalName, sspi.SECPKG_CRED_INBOUND, nil)
|
|
}
|
|
|
|
func updateContext(c *sspi.Context, dst, src []byte, targetName *uint16) (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(targetName, 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
|
|
}
|
|
|
|
func makeSignature(c *sspi.Context, msg []byte, qop, seqno uint32) ([]byte, error) {
|
|
_, maxSignature, _, _, err := c.Sizes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if maxSignature == 0 {
|
|
return nil, errors.New("integrity services are not requested or unavailable")
|
|
}
|
|
|
|
var b [2]sspi.SecBuffer
|
|
b[0].Set(sspi.SECBUFFER_DATA, msg)
|
|
b[1].Set(sspi.SECBUFFER_TOKEN, make([]byte, maxSignature))
|
|
|
|
ret := sspi.MakeSignature(c.Handle, qop, sspi.NewSecBufferDesc(b[:]), seqno)
|
|
if ret != sspi.SEC_E_OK {
|
|
return nil, ret
|
|
}
|
|
|
|
return b[1].Bytes(), nil
|
|
}
|
|
|
|
func encryptMessage(c *sspi.Context, msg []byte, qop, seqno uint32) ([]byte, error) {
|
|
_ /*maxToken*/, maxSignature, cBlockSize, cSecurityTrailer, err := c.Sizes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if maxSignature == 0 {
|
|
return nil, errors.New("integrity services are not requested or unavailable")
|
|
}
|
|
|
|
var b [3]sspi.SecBuffer
|
|
b[0].Set(sspi.SECBUFFER_TOKEN, make([]byte, cSecurityTrailer))
|
|
b[1].Set(sspi.SECBUFFER_DATA, msg)
|
|
b[2].Set(sspi.SECBUFFER_PADDING, make([]byte, cBlockSize))
|
|
|
|
ret := sspi.EncryptMessage(c.Handle, qop, sspi.NewSecBufferDesc(b[:]), seqno)
|
|
if ret != sspi.SEC_E_OK {
|
|
return nil, ret
|
|
}
|
|
|
|
r0, r1, r2 := b[0].Bytes(), b[1].Bytes(), b[2].Bytes()
|
|
res := make([]byte, 0, len(r0)+len(r1)+len(r2))
|
|
res = append(res, r0...)
|
|
res = append(res, r1...)
|
|
res = append(res, r2...)
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func decryptMessage(c *sspi.Context, msg []byte, seqno uint32) (uint32, []byte, error) {
|
|
var b [2]sspi.SecBuffer
|
|
b[0].Set(sspi.SECBUFFER_STREAM, msg)
|
|
b[1].Set(sspi.SECBUFFER_DATA, []byte{})
|
|
|
|
var qop uint32
|
|
ret := sspi.DecryptMessage(c.Handle, sspi.NewSecBufferDesc(b[:]), seqno, &qop)
|
|
if ret != sspi.SEC_E_OK {
|
|
return qop, nil, ret
|
|
}
|
|
|
|
return qop, b[1].Bytes(), nil
|
|
}
|
|
|
|
func verifySignature(c *sspi.Context, msg, token []byte, seqno uint32) (uint32, error) {
|
|
var b [2]sspi.SecBuffer
|
|
b[0].Set(sspi.SECBUFFER_DATA, msg)
|
|
b[1].Set(sspi.SECBUFFER_TOKEN, token)
|
|
|
|
var qop uint32
|
|
|
|
ret := sspi.VerifySignature(c.Handle, sspi.NewSecBufferDesc(b[:]), seqno, &qop)
|
|
if ret != sspi.SEC_E_OK {
|
|
return 0, ret
|
|
}
|
|
|
|
return qop, nil
|
|
}
|
|
|
|
// ClientContext is used by the client to manage all steps of Negotiate negotiation.
|
|
type ClientContext struct {
|
|
sctxt *sspi.Context
|
|
targetName *uint16
|
|
}
|
|
|
|
// NewClientContext creates a new client context. It uses client
|
|
// credentials cred generated by AcquireCurrentUserCredentials or
|
|
// AcquireUserCredentials and SPN to start a client Negotiate
|
|
// negotiation sequence. targetName is the service principal name
|
|
// (SPN) or the security context of the destination server.
|
|
// NewClientContext returns a new token to be sent to the server.
|
|
func NewClientContext(cred *sspi.Credentials, targetName string) (cc *ClientContext, outputToken []byte, err error) {
|
|
return NewClientContextWithFlags(cred, targetName, sspi.ISC_REQ_CONNECTION)
|
|
}
|
|
|
|
// NewClientContextWithFlags creates a new client context. It uses client
|
|
// credentials cred generated by AcquireCurrentUserCredentials or
|
|
// AcquireUserCredentials and SPN to start a client Negotiate
|
|
// negotiation sequence. targetName is the service principal name
|
|
// (SPN) or the security context of the destination server.
|
|
// The flags parameter is used to indicate requests for the context
|
|
// (for example sspi.ISC_REQ_CONFIDENTIALITY|sspi.ISC_REQ_REPLAY_DETECT)
|
|
// NewClientContextWithFlags returns a new token to be sent to the server.
|
|
func NewClientContextWithFlags(cred *sspi.Credentials, targetName string, flags uint32) (cc *ClientContext, outputToken []byte, err error) {
|
|
var tname *uint16
|
|
if len(targetName) > 0 {
|
|
p, err2 := syscall.UTF16FromString(targetName)
|
|
if err2 != nil {
|
|
return nil, nil, err2
|
|
}
|
|
if len(p) > 0 {
|
|
tname = &p[0]
|
|
}
|
|
}
|
|
otoken := make([]byte, PackageInfo.MaxToken)
|
|
c := sspi.NewClientContext(cred, flags)
|
|
|
|
authCompleted, n, err2 := updateContext(c, otoken, nil, tname)
|
|
if err2 != nil {
|
|
return nil, nil, err2
|
|
}
|
|
if authCompleted {
|
|
c.Release()
|
|
return nil, nil, errors.New("negotiate authentication should not be completed yet")
|
|
}
|
|
if n == 0 {
|
|
c.Release()
|
|
return nil, nil, errors.New("negotiate token should not be empty")
|
|
}
|
|
otoken = otoken[:n]
|
|
return &ClientContext{sctxt: c, targetName: tname}, otoken, nil
|
|
}
|
|
|
|
// Release free up resources associated with client context c.
|
|
func (c *ClientContext) Release() error {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.sctxt.Release()
|
|
}
|
|
|
|
// Expiry returns c expiry time.
|
|
func (c *ClientContext) Expiry() time.Time {
|
|
return c.sctxt.Expiry()
|
|
}
|
|
|
|
// Update advances client part of Negotiate negotiation c. It uses
|
|
// token received from the server and returns true if client part
|
|
// of authentication is complete. It also returns new token to be
|
|
// sent to the server.
|
|
func (c *ClientContext) Update(token []byte) (authCompleted bool, outputToken []byte, err error) {
|
|
otoken := make([]byte, PackageInfo.MaxToken)
|
|
authDone, n, err2 := updateContext(c.sctxt, otoken, token, c.targetName)
|
|
if err2 != nil {
|
|
return false, nil, err2
|
|
}
|
|
if n == 0 && !authDone {
|
|
return false, nil, errors.New("negotiate token should not be empty")
|
|
}
|
|
otoken = otoken[:n]
|
|
return authDone, otoken, 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()
|
|
}
|
|
|
|
// MakeSignature uses the established client context to create a signature
|
|
// for the given message using the provided quality of protection flags and
|
|
// sequence number. It returns the signature token in addition to any error.
|
|
func (c *ClientContext) MakeSignature(msg []byte, qop, seqno uint32) ([]byte, error) {
|
|
return makeSignature(c.sctxt, msg, qop, seqno)
|
|
}
|
|
|
|
// VerifySignature uses the established client context and signature token
|
|
// to check that the provided message hasn't been tampered or received out
|
|
// of sequence. It returns any quality of protection flags and any error
|
|
// that occurred.
|
|
func (c *ClientContext) VerifySignature(msg, token []byte, seqno uint32) (uint32, error) {
|
|
return verifySignature(c.sctxt, msg, token, seqno)
|
|
}
|
|
|
|
// EncryptMessage uses the established client context to encrypt a message
|
|
// using the provided quality of protection flags and sequence number.
|
|
// It returns the signature token in addition to any error.
|
|
// IMPORTANT: the input msg parameter is updated in place by the low-level windows api
|
|
// so must be copied if the initial content should not be modified.
|
|
func (c *ClientContext) EncryptMessage(msg []byte, qop, seqno uint32) ([]byte, error) {
|
|
return encryptMessage(c.sctxt, msg, qop, seqno)
|
|
}
|
|
|
|
// DecryptMessage uses the established client context to decrypt a message
|
|
// using the provided sequence number.
|
|
// It returns the quality of protection flag and the decrypted message in addition to any error.
|
|
func (c *ClientContext) DecryptMessage(msg []byte, seqno uint32) (uint32, []byte, error) {
|
|
return decryptMessage(c.sctxt, msg, seqno)
|
|
}
|
|
|
|
// VerifyFlags determines if all flags used to construct the client context
|
|
// were honored (see NewClientContextWithFlags). It should be called after c.Update.
|
|
func (c *ClientContext) VerifyFlags() error {
|
|
return c.sctxt.VerifyFlags()
|
|
}
|
|
|
|
// VerifySelectiveFlags determines if the given flags were honored (see NewClientContextWithFlags).
|
|
// It should be called after c.Update.
|
|
func (c *ClientContext) VerifySelectiveFlags(flags uint32) error {
|
|
return c.sctxt.VerifySelectiveFlags(flags)
|
|
}
|
|
|
|
// ServerContext is used by the server to manage all steps of Negotiate
|
|
// 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 token from
|
|
// the client to start server Negotiate negotiation sequence.
|
|
// It also returns new token to be sent to the client.
|
|
func NewServerContext(cred *sspi.Credentials, token []byte) (sc *ServerContext, authDone bool, outputToken []byte, err error) {
|
|
otoken := make([]byte, PackageInfo.MaxToken)
|
|
c := sspi.NewServerContext(cred, sspi.ASC_REQ_CONNECTION)
|
|
authDone, n, err2 := updateContext(c, otoken, token, nil)
|
|
if err2 != nil {
|
|
return nil, false, nil, err2
|
|
}
|
|
otoken = otoken[:n]
|
|
return &ServerContext{sctxt: c}, authDone, otoken, nil
|
|
}
|
|
|
|
// Release free up resources associated with server context c.
|
|
func (c *ServerContext) Release() error {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.sctxt.Release()
|
|
}
|
|
|
|
// Expiry returns c expiry time.
|
|
func (c *ServerContext) Expiry() time.Time {
|
|
return c.sctxt.Expiry()
|
|
}
|
|
|
|
// Update advances server part of Negotiate negotiation c. It uses
|
|
// token received from the client and returns true if server part
|
|
// of authentication is complete. It also returns new token to be
|
|
// sent to the client.
|
|
func (c *ServerContext) Update(token []byte) (authCompleted bool, outputToken []byte, err error) {
|
|
otoken := make([]byte, PackageInfo.MaxToken)
|
|
authDone, n, err2 := updateContext(c.sctxt, otoken, token, nil)
|
|
if err2 != nil {
|
|
return false, nil, err2
|
|
}
|
|
if n == 0 && !authDone {
|
|
return false, nil, errors.New("negotiate token should not be empty")
|
|
}
|
|
otoken = otoken[:n]
|
|
return authDone, otoken, nil
|
|
}
|
|
|
|
const _SECPKG_ATTR_NATIVE_NAMES = 13
|
|
|
|
type _SecPkgContext_NativeNames struct {
|
|
ClientName *uint16
|
|
ServerName *uint16
|
|
}
|
|
|
|
// GetUsername returns the username corresponding to the authenticated client
|
|
func (c *ServerContext) GetUsername() (string, error) {
|
|
var ns _SecPkgContext_NativeNames
|
|
ret := sspi.QueryContextAttributes(c.sctxt.Handle, _SECPKG_ATTR_NATIVE_NAMES, (*byte)(unsafe.Pointer(&ns)))
|
|
if ret != sspi.SEC_E_OK {
|
|
return "", ret
|
|
}
|
|
sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ns.ServerName)))
|
|
defer sspi.FreeContextBuffer((*byte)(unsafe.Pointer(ns.ClientName)))
|
|
return syscall.UTF16ToString((*[2 << 20]uint16)(unsafe.Pointer(ns.ClientName))[:]), 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()
|
|
}
|
|
|
|
// MakeSignature uses the established server context to create a signature
|
|
// for the given message using the provided quality of protection flags and
|
|
// sequence number. It returns the signature token in addition to any error.
|
|
func (c *ServerContext) MakeSignature(msg []byte, qop, seqno uint32) ([]byte, error) {
|
|
return makeSignature(c.sctxt, msg, qop, seqno)
|
|
}
|
|
|
|
// VerifySignature uses the established server context and signature token
|
|
// to check that the provided message hasn't been tampered or received out
|
|
// of sequence. It returns any quality of protection flags and any error
|
|
// that occurred.
|
|
func (c *ServerContext) VerifySignature(msg, token []byte, seqno uint32) (uint32, error) {
|
|
return verifySignature(c.sctxt, msg, token, seqno)
|
|
}
|
|
|
|
// EncryptMessage uses the established server context to encrypt a message
|
|
// using the provided quality of protection flags and sequence number.
|
|
// It returns the signature token in addition to any error.
|
|
// IMPORTANT: the input msg parameter is updated in place by the low-level windows api
|
|
// so must be copied if the initial content should not be modified.
|
|
func (c *ServerContext) EncryptMessage(msg []byte, qop, seqno uint32) ([]byte, error) {
|
|
return encryptMessage(c.sctxt, msg, qop, seqno)
|
|
}
|
|
|
|
// DecryptMessage uses the established server context to decrypt a message
|
|
// using the provided sequence number.
|
|
// It returns the quality of protection flag and the decrypted message in addition to any error.
|
|
func (c *ServerContext) DecryptMessage(msg []byte, seqno uint32) (uint32, []byte, error) {
|
|
return decryptMessage(c.sctxt, msg, seqno)
|
|
}
|