diff --git a/internal/common/common.go b/internal/common/common.go index 7ccf4d5..a5b64ac 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows package common @@ -41,19 +42,34 @@ func BuildAuthIdentity(domain, username, password string) (*sspi.SEC_WINNT_AUTH_ } func UpdateContext(c *sspi.Context, dst, src []byte, targetName *uint16) (authCompleted bool, n int, err error) { - var inBuf, outBuf [1]sspi.SecBuffer + return UpdateContextWithChannelBindings(c, dst, src, nil, targetName) +} + +// UpdateContextWithChannelBindings performs SSPI context update with optional channel binding tokens. +func UpdateContextWithChannelBindings(c *sspi.Context, dst, src, channelBindings []byte, targetName *uint16) (authCompleted bool, n int, err error) { + var inBuf [2]sspi.SecBuffer + inBuf[0].Set(sspi.SECBUFFER_TOKEN, src) inBufs := &sspi.SecBufferDesc{ Version: sspi.SECBUFFER_VERSION, BuffersCount: 1, Buffers: &inBuf[0], } + + if len(channelBindings) > 0 { + // With channel bindings: TOKEN buffer + CHANNEL_BINDINGS buffer + inBuf[1].Set(sspi.SECBUFFER_CHANNEL_BINDINGS, channelBindings) + inBufs.BuffersCount = 2 + } + + var outBuf [1]sspi.SecBuffer 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: diff --git a/kerberos/kerberos.go b/kerberos/kerberos.go index 00f752e..23d96ba 100644 --- a/kerberos/kerberos.go +++ b/kerberos/kerberos.go @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows // Package kerberos provides access to the Microsoft Kerberos SSP Package. -// package kerberos import ( @@ -74,8 +74,9 @@ func AcquireServerCredentials(principalName string) (*sspi.Credentials, error) { // ClientContext is used by the client to manage all steps of Kerberos negotiation. type ClientContext struct { - sctxt *sspi.Context - targetName *uint16 + sctxt *sspi.Context + targetName *uint16 + channelBindings []byte } // NewClientContext creates a new client context. It uses client @@ -97,6 +98,13 @@ func NewClientContext(cred *sspi.Credentials, targetName string) (*ClientContext // (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) (*ClientContext, bool, []byte, error) { + return NewClientContextWithChannelBindings(cred, targetName, flags, nil) +} + +// NewClientContextWithChannelBindings creates a new client context with channel binding support. +// channelBindings should contain the channel binding token (e.g., TLS channel binding data). +// https://learn.microsoft.com/en-us/windows/win32/secauthn/epa-support-in-service +func NewClientContextWithChannelBindings(cred *sspi.Credentials, targetName string, flags uint32, channelBindings []byte) (*ClientContext, bool, []byte, error) { var tname *uint16 if len(targetName) > 0 { p, err := syscall.UTF16FromString(targetName) @@ -110,7 +118,7 @@ func NewClientContextWithFlags(cred *sspi.Credentials, targetName string, flags otoken := make([]byte, PackageInfo.MaxToken) c := sspi.NewClientContext(cred, flags) - authCompleted, n, err := common.UpdateContext(c, otoken, nil, tname) + authCompleted, n, err := common.UpdateContextWithChannelBindings(c, otoken, nil, channelBindings, tname) if err != nil { return nil, false, nil, err } @@ -119,7 +127,7 @@ func NewClientContextWithFlags(cred *sspi.Credentials, targetName string, flags return nil, false, nil, errors.New("kerberos token should not be empty") } otoken = otoken[:n] - return &ClientContext{sctxt: c, targetName: tname}, authCompleted, otoken, nil + return &ClientContext{sctxt: c, targetName: tname, channelBindings: channelBindings}, authCompleted, otoken, nil } // Release free up resources associated with client context c. @@ -141,7 +149,8 @@ func (c *ClientContext) Expiry() time.Time { // sent to the server. func (c *ClientContext) Update(token []byte) (bool, []byte, error) { otoken := make([]byte, PackageInfo.MaxToken) - authDone, n, err := common.UpdateContext(c.sctxt, otoken, token, c.targetName) + + authDone, n, err := common.UpdateContextWithChannelBindings(c.sctxt, otoken, token, c.channelBindings, c.targetName) if err != nil { return false, nil, err } diff --git a/kerberos/kerberos_test.go b/kerberos/kerberos_test.go index 4cae0fa..d8b8860 100644 --- a/kerberos/kerberos_test.go +++ b/kerberos/kerberos_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows package kerberos_test diff --git a/syscall.go b/syscall.go index 04660df..27aa4f8 100644 --- a/syscall.go +++ b/syscall.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows package sspi @@ -96,19 +97,20 @@ type CredHandle struct { const ( SECURITY_NATIVE_DREP = 16 - SECBUFFER_DATA = 1 - SECBUFFER_TOKEN = 2 - SECBUFFER_PKG_PARAMS = 3 - SECBUFFER_MISSING = 4 - SECBUFFER_EXTRA = 5 - SECBUFFER_STREAM_TRAILER = 6 - SECBUFFER_STREAM_HEADER = 7 - SECBUFFER_PADDING = 9 - SECBUFFER_STREAM = 10 - SECBUFFER_READONLY = 0x80000000 - SECBUFFER_ATTRMASK = 0xf0000000 - SECBUFFER_VERSION = 0 - SECBUFFER_EMPTY = 0 + SECBUFFER_DATA = 1 + SECBUFFER_TOKEN = 2 + SECBUFFER_PKG_PARAMS = 3 + SECBUFFER_MISSING = 4 + SECBUFFER_EXTRA = 5 + SECBUFFER_STREAM_TRAILER = 6 + SECBUFFER_STREAM_HEADER = 7 + SECBUFFER_PADDING = 9 + SECBUFFER_STREAM = 10 + SECBUFFER_CHANNEL_BINDINGS = 14 + SECBUFFER_READONLY = 0x80000000 + SECBUFFER_ATTRMASK = 0xf0000000 + SECBUFFER_VERSION = 0 + SECBUFFER_EMPTY = 0 ISC_REQ_DELEGATE = 1 ISC_REQ_MUTUAL_AUTH = 2