fix(go.sum): update ResolveSpec dependency to v1.0.87
This commit is contained in:
+73
@@ -0,0 +1,73 @@
|
||||
package integratedauth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/microsoft/go-mssqldb/msdsn"
|
||||
)
|
||||
|
||||
var (
|
||||
providers map[string]Provider
|
||||
DefaultProviderName string
|
||||
|
||||
ErrProviderCannotBeNil = errors.New("provider cannot be nil")
|
||||
ErrProviderNameMustBePopulated = errors.New("provider name must be populated")
|
||||
)
|
||||
|
||||
func init() {
|
||||
providers = make(map[string]Provider)
|
||||
}
|
||||
|
||||
// GetIntegratedAuthenticator calls the authProvider specified in the 'authenticator' connection string parameter, if supplied.
|
||||
// Otherwise fails back to the DefaultProviderName implementation for the platform.
|
||||
func GetIntegratedAuthenticator(config msdsn.Config) (IntegratedAuthenticator, error) {
|
||||
authenticatorName, ok := config.Parameters["authenticator"]
|
||||
if !ok {
|
||||
provider, err := getProvider(DefaultProviderName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, err := provider.GetIntegratedAuthenticator(config)
|
||||
// we ignore the error in this case to force a fallback to sqlserver authentication.
|
||||
// this preserves the original behaviour
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
provider, err := getProvider(authenticatorName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return provider.GetIntegratedAuthenticator(config)
|
||||
}
|
||||
|
||||
func getProvider(name string) (Provider, error) {
|
||||
provider, ok := providers[name]
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("provider %v not found", name)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// SetIntegratedAuthenticationProvider stores a named authentication provider. It should be called before any connections are created.
|
||||
func SetIntegratedAuthenticationProvider(providerName string, p Provider) error {
|
||||
if p == nil {
|
||||
return ErrProviderCannotBeNil
|
||||
}
|
||||
|
||||
if providerName == "" {
|
||||
return ErrProviderNameMustBePopulated
|
||||
}
|
||||
|
||||
providers[providerName] = p
|
||||
|
||||
return nil
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
package integratedauth
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/md5"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type AuthenticatorWithEPA interface {
|
||||
SetChannelBinding(*ChannelBindings)
|
||||
}
|
||||
|
||||
type ChannelBindingsType uint32
|
||||
const (
|
||||
ChannelBindingsTypeTLSExporter = 0
|
||||
ChannelBindingsTypeTLSUnique = 1
|
||||
ChannelBindingsTypeTLSServerEndPoint = 2
|
||||
ChannelBindingsTypeEmpty = 3
|
||||
)
|
||||
|
||||
const (
|
||||
// https://datatracker.ietf.org/doc/rfc9266/
|
||||
TLS_EXPORTER_PREFIX = "tls-exporter:"
|
||||
TLS_EXPORTER_EKM_LABEL = "EXPORTER-Channel-Binding"
|
||||
TLS_EXPORTER_EKM_LENGTH = 32
|
||||
// https://www.rfc-editor.org/rfc/rfc5801.html#section-5.2
|
||||
TLS_UNIQUE_PREFIX = "tls-unique:"
|
||||
TLS_SERVER_END_POINT_PREFIX = "tls-server-end-point:"
|
||||
)
|
||||
|
||||
// gss_channel_bindings_struct: https://docs.oracle.com/cd/E19683-01/816-1331/overview-52/index.html
|
||||
// gss_buffer_desc: https://docs.oracle.com/cd/E19683-01/816-1331/reference-21/index.html
|
||||
type ChannelBindings struct {
|
||||
Type ChannelBindingsType
|
||||
InitiatorAddrType uint32
|
||||
InitiatorAddress []byte
|
||||
AcceptorAddrType uint32
|
||||
AcceptorAddress []byte
|
||||
ApplicationData []byte
|
||||
}
|
||||
|
||||
// SEC_CHANNEL_BINDINGS: https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-sec_channel_bindings
|
||||
type SEC_CHANNEL_BINDINGS struct {
|
||||
DwInitiatorAddrType uint32
|
||||
CbInitiatorLength uint32
|
||||
DwInitiatorOffset uint32
|
||||
DwAcceptorAddrType uint32
|
||||
CbAcceptorLength uint32
|
||||
DwAcceptorOffset uint32
|
||||
CbApplicationDataLength uint32
|
||||
DwApplicationDataOffset uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
var EmptyChannelBindings = &ChannelBindings{
|
||||
Type: ChannelBindingsTypeEmpty,
|
||||
InitiatorAddrType: 0,
|
||||
InitiatorAddress: nil,
|
||||
AcceptorAddrType: 0,
|
||||
AcceptorAddress: nil,
|
||||
ApplicationData: nil,
|
||||
}
|
||||
|
||||
// ToBytes converts a ChannelBindings struct to a byte slice as it would be gss_channel_bindings_struct structure in GSSAPI.
|
||||
// Returns:
|
||||
// - a byte slice
|
||||
func (cb *ChannelBindings) ToBytes() []byte {
|
||||
binarylength := 4 + 4 + 4 + 4 + 4 + uint32(len(cb.InitiatorAddress)+len(cb.AcceptorAddress)+len(cb.ApplicationData))
|
||||
i := 0
|
||||
bytes := make([]byte, binarylength)
|
||||
binary.LittleEndian.PutUint32(bytes[i:i+4], cb.InitiatorAddrType)
|
||||
i += 4
|
||||
binary.LittleEndian.PutUint32(bytes[i:i+4], uint32(len(cb.InitiatorAddress)))
|
||||
i += 4
|
||||
if len(cb.InitiatorAddress) > 0 {
|
||||
copy(bytes[i:i+len(cb.InitiatorAddress)], cb.InitiatorAddress)
|
||||
i += len(cb.InitiatorAddress)
|
||||
}
|
||||
binary.LittleEndian.PutUint32(bytes[i:i+4], cb.AcceptorAddrType)
|
||||
i += 4
|
||||
binary.LittleEndian.PutUint32(bytes[i:i+4], uint32(len(cb.AcceptorAddress)))
|
||||
i += 4
|
||||
if len(cb.AcceptorAddress) > 0 {
|
||||
copy(bytes[i:i+len(cb.AcceptorAddress)], cb.AcceptorAddress)
|
||||
i += len(cb.AcceptorAddress)
|
||||
}
|
||||
binary.LittleEndian.PutUint32(bytes[i:i+4], uint32(len(cb.ApplicationData)))
|
||||
i += 4
|
||||
if len(cb.ApplicationData) > 0 {
|
||||
copy(bytes[i:i+len(cb.ApplicationData)], cb.ApplicationData)
|
||||
i += len(cb.ApplicationData)
|
||||
}
|
||||
// Print bytes in hexdump -C style for debugging
|
||||
return bytes
|
||||
}
|
||||
|
||||
// Md5Hash calculates the MD5 hash of the ChannelBindings struct
|
||||
// Returns:
|
||||
// - a byte slice
|
||||
func (cb *ChannelBindings) Md5Hash() []byte {
|
||||
if cb.Type == ChannelBindingsTypeEmpty {
|
||||
// generate a slice with zeros
|
||||
zeros := make([]byte, 16)
|
||||
return zeros
|
||||
}
|
||||
hash := md5.New()
|
||||
hash.Write(cb.ToBytes())
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
// AsSSPI_SEC_CHANNEL_BINDINGS converts a ChannelBindings struct to a SEC_CHANNEL_BINDINGS struct
|
||||
// Returns:
|
||||
// - a SEC_CHANNEL_BINDINGS struct
|
||||
func (cb *ChannelBindings) AsSSPI_SEC_CHANNEL_BINDINGS() *SEC_CHANNEL_BINDINGS {
|
||||
initiatorOffset := uint32(32)
|
||||
acceptorOffset := initiatorOffset + uint32(len(cb.InitiatorAddress))
|
||||
applicationDataOffset := acceptorOffset + uint32(len(cb.AcceptorAddress))
|
||||
c := &SEC_CHANNEL_BINDINGS{
|
||||
DwInitiatorAddrType: cb.InitiatorAddrType,
|
||||
CbInitiatorLength: uint32(len(cb.InitiatorAddress)),
|
||||
DwInitiatorOffset: initiatorOffset,
|
||||
DwAcceptorAddrType: cb.AcceptorAddrType,
|
||||
CbAcceptorLength: uint32(len(cb.AcceptorAddress)),
|
||||
DwAcceptorOffset: acceptorOffset,
|
||||
CbApplicationDataLength: uint32(len(cb.ApplicationData)),
|
||||
DwApplicationDataOffset: applicationDataOffset,
|
||||
}
|
||||
data := make([]byte, c.CbInitiatorLength+c.CbAcceptorLength+c.CbApplicationDataLength)
|
||||
var i uint32 = 0
|
||||
if c.CbInitiatorLength > 0 {
|
||||
copy(data[i:i+c.CbInitiatorLength], cb.InitiatorAddress)
|
||||
i += c.CbInitiatorLength
|
||||
}
|
||||
if c.CbAcceptorLength > 0 {
|
||||
copy(data[i:i+c.CbAcceptorLength], cb.AcceptorAddress)
|
||||
i += c.CbAcceptorLength
|
||||
}
|
||||
if c.CbApplicationDataLength > 0 {
|
||||
copy(data[i:i+c.CbApplicationDataLength], cb.ApplicationData)
|
||||
i += c.CbApplicationDataLength
|
||||
}
|
||||
c.Data = data
|
||||
return c
|
||||
}
|
||||
|
||||
// ToBytes converts a SEC_CHANNEL_BINDINGS struct to a byte slice, that can be use in SSPI InitializeSecurityContext function.
|
||||
// Returns:
|
||||
// - a byte slice
|
||||
func (cb *SEC_CHANNEL_BINDINGS) ToBytes() []byte {
|
||||
bytes := make([]byte, 32+len(cb.Data))
|
||||
binary.LittleEndian.PutUint32(bytes[0:4], cb.DwInitiatorAddrType)
|
||||
binary.LittleEndian.PutUint32(bytes[4:8], cb.CbInitiatorLength)
|
||||
binary.LittleEndian.PutUint32(bytes[8:12], cb.DwInitiatorOffset)
|
||||
binary.LittleEndian.PutUint32(bytes[12:16], cb.DwAcceptorAddrType)
|
||||
binary.LittleEndian.PutUint32(bytes[16:20], cb.CbAcceptorLength)
|
||||
binary.LittleEndian.PutUint32(bytes[20:24], cb.DwAcceptorOffset)
|
||||
binary.LittleEndian.PutUint32(bytes[24:28], cb.CbApplicationDataLength)
|
||||
binary.LittleEndian.PutUint32(bytes[28:32], cb.DwApplicationDataOffset)
|
||||
copy(bytes[32:32+len(cb.Data)], cb.Data)
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
// GenerateCBTFromTLSUnique generates a ChannelBindings struct from a TLS unique value
|
||||
// Adds tls-unique: prefix to the TLS unique value.
|
||||
// Parameters:
|
||||
// - tlsUnique: the TLS unique value
|
||||
// Returns:
|
||||
// - a ChannelBindings struct
|
||||
func GenerateCBTFromTLSUnique(tlsUnique []byte) (*ChannelBindings, error) {
|
||||
if len(tlsUnique) == 0 {
|
||||
return nil, fmt.Errorf("tlsUnique is empty")
|
||||
}
|
||||
return &ChannelBindings{
|
||||
Type: ChannelBindingsTypeTLSUnique,
|
||||
InitiatorAddrType: 0,
|
||||
InitiatorAddress: nil,
|
||||
AcceptorAddrType: 0,
|
||||
AcceptorAddress: nil,
|
||||
ApplicationData: append([]byte(TLS_UNIQUE_PREFIX), tlsUnique...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateCBTFromTLSConnState generates a ChannelBindings struct from a TLS connection state
|
||||
// If the TLS version is TLS 1.3, it generates a ChannelBindings struct from the TLS exporter key.
|
||||
// If the TLS version is not TLS 1.3, it generates a ChannelBindings struct from the TLS unique value.
|
||||
// Parameters:
|
||||
// - state: the TLS connection state
|
||||
// Returns:
|
||||
// - a ChannelBindings struct
|
||||
func GenerateCBTFromTLSConnState(state tls.ConnectionState) (*ChannelBindings, error) {
|
||||
switch state.Version {
|
||||
case tls.VersionTLS13:
|
||||
// We don't support generating Channel Bindings from TLS 1.3 yet
|
||||
return nil, nil
|
||||
default:
|
||||
return GenerateCBTFromTLSUnique(state.TLSUnique)
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateCBTFromTLSExporter generates a ChannelBindings struct from a TLS exporter key
|
||||
// Parameters:
|
||||
// - exporterKey: the TLS exporter key
|
||||
// Returns:
|
||||
// - a ChannelBindings struct
|
||||
func GenerateCBTFromTLSExporter(exporterKey []byte) (*ChannelBindings, error) {
|
||||
if len(exporterKey) == 0 {
|
||||
return nil, fmt.Errorf("exporterKey is empty")
|
||||
}
|
||||
|
||||
return &ChannelBindings{
|
||||
Type: ChannelBindingsTypeTLSExporter,
|
||||
InitiatorAddrType: 0,
|
||||
InitiatorAddress: nil,
|
||||
AcceptorAddrType: 0,
|
||||
AcceptorAddress: nil,
|
||||
ApplicationData: append([]byte(TLS_EXPORTER_PREFIX), exporterKey...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateCBTFromServerCert generates a ChannelBindings struct from a server certificate
|
||||
// Calculates the hash of the server certificate as described in 4.2 section of RFC5056.
|
||||
// Parameters:
|
||||
// - cert: the server certificate
|
||||
// Returns:
|
||||
// - a ChannelBindings struct
|
||||
func GenerateCBTFromServerCert(cert *x509.Certificate) *ChannelBindings {
|
||||
if cert == nil {
|
||||
return nil
|
||||
}
|
||||
var certHash []byte
|
||||
var hashType crypto.Hash
|
||||
switch cert.SignatureAlgorithm {
|
||||
case x509.SHA256WithRSA, x509.ECDSAWithSHA256, x509.SHA256WithRSAPSS:
|
||||
hashType = crypto.SHA256
|
||||
case x509.SHA384WithRSA, x509.ECDSAWithSHA384, x509.SHA384WithRSAPSS:
|
||||
hashType = crypto.SHA384
|
||||
case x509.SHA512WithRSA, x509.ECDSAWithSHA512, x509.SHA512WithRSAPSS:
|
||||
hashType = crypto.SHA512
|
||||
default:
|
||||
hashType = crypto.SHA256
|
||||
}
|
||||
h := hashType.New()
|
||||
_, _ = h.Write(cert.Raw)
|
||||
certHash = h.Sum(nil)
|
||||
return &ChannelBindings{
|
||||
Type: ChannelBindingsTypeTLSServerEndPoint,
|
||||
InitiatorAddrType: 0,
|
||||
InitiatorAddress: nil,
|
||||
AcceptorAddrType: 0,
|
||||
AcceptorAddress: nil,
|
||||
ApplicationData: append([]byte(TLS_SERVER_END_POINT_PREFIX), certHash...),
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package integratedauth
|
||||
|
||||
import (
|
||||
"github.com/microsoft/go-mssqldb/msdsn"
|
||||
)
|
||||
|
||||
// Provider returns an SSPI compatible authentication provider
|
||||
type Provider interface {
|
||||
// GetIntegratedAuthenticator is responsible for returning an instance of the required IntegratedAuthenticator interface
|
||||
GetIntegratedAuthenticator(config msdsn.Config) (IntegratedAuthenticator, error)
|
||||
}
|
||||
|
||||
// IntegratedAuthenticator is the interface for SSPI Login Authentication providers
|
||||
type IntegratedAuthenticator interface {
|
||||
InitialBytes() ([]byte, error)
|
||||
NextBytes([]byte) ([]byte, error)
|
||||
Free()
|
||||
}
|
||||
|
||||
// ProviderFunc is an adapter to convert a GetIntegratedAuthenticator func into a Provider
|
||||
type ProviderFunc func(config msdsn.Config) (IntegratedAuthenticator, error)
|
||||
|
||||
func (f ProviderFunc) GetIntegratedAuthenticator(config msdsn.Config) (IntegratedAuthenticator, error) {
|
||||
return f(config)
|
||||
}
|
||||
+423
@@ -0,0 +1,423 @@
|
||||
package ntlm
|
||||
|
||||
import (
|
||||
"crypto/des"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/microsoft/go-mssqldb/integratedauth"
|
||||
"github.com/microsoft/go-mssqldb/msdsn"
|
||||
|
||||
//lint:ignore SA1019 MD4 is used by legacy NTLM
|
||||
"golang.org/x/crypto/md4"
|
||||
)
|
||||
|
||||
const (
|
||||
_NEGOTIATE_MESSAGE = 1
|
||||
_CHALLENGE_MESSAGE = 2
|
||||
_AUTHENTICATE_MESSAGE = 3
|
||||
)
|
||||
|
||||
const (
|
||||
_NEGOTIATE_UNICODE = 0x00000001
|
||||
_NEGOTIATE_OEM = 0x00000002
|
||||
_NEGOTIATE_TARGET = 0x00000004
|
||||
_NEGOTIATE_SIGN = 0x00000010
|
||||
_NEGOTIATE_SEAL = 0x00000020
|
||||
_NEGOTIATE_DATAGRAM = 0x00000040
|
||||
_NEGOTIATE_LMKEY = 0x00000080
|
||||
_NEGOTIATE_NTLM = 0x00000200
|
||||
_NEGOTIATE_ANONYMOUS = 0x00000800
|
||||
_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000
|
||||
_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000
|
||||
_NEGOTIATE_ALWAYS_SIGN = 0x00008000
|
||||
_NEGOTIATE_TARGET_TYPE_DOMAIN = 0x00010000
|
||||
_NEGOTIATE_TARGET_TYPE_SERVER = 0x00020000
|
||||
_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000
|
||||
_NEGOTIATE_IDENTIFY = 0x00100000
|
||||
_REQUEST_NON_NT_SESSION_KEY = 0x00400000
|
||||
_NEGOTIATE_TARGET_INFO = 0x00800000
|
||||
_NEGOTIATE_VERSION = 0x02000000
|
||||
_NEGOTIATE_128 = 0x20000000
|
||||
_NEGOTIATE_KEY_EXCH = 0x40000000
|
||||
_NEGOTIATE_56 = 0x80000000
|
||||
)
|
||||
|
||||
const _NEGOTIATE_FLAGS = _NEGOTIATE_UNICODE |
|
||||
_NEGOTIATE_NTLM |
|
||||
_NEGOTIATE_OEM_DOMAIN_SUPPLIED |
|
||||
_NEGOTIATE_OEM_WORKSTATION_SUPPLIED |
|
||||
_NEGOTIATE_ALWAYS_SIGN |
|
||||
_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||
|
||||
const (
|
||||
AV_PAIR_MsvAvChannelBindings = 0x000A
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
Domain string
|
||||
UserName string
|
||||
Password string
|
||||
Workstation string
|
||||
ChannelBinding []byte
|
||||
}
|
||||
|
||||
func (auth *Auth) SetChannelBinding(channelBinding *integratedauth.ChannelBindings) {
|
||||
if channelBinding.Type == integratedauth.ChannelBindingsTypeTLSExporter {
|
||||
auth.ChannelBinding = channelBinding.ApplicationData
|
||||
} else {
|
||||
auth.ChannelBinding = channelBinding.Md5Hash()
|
||||
}
|
||||
}
|
||||
|
||||
// getAuth returns an authentication handle Auth to provide authentication content
|
||||
// to mssql.connect
|
||||
func getAuth(config msdsn.Config) (integratedauth.IntegratedAuthenticator, error) {
|
||||
if !strings.ContainsRune(config.User, '\\') {
|
||||
return nil, fmt.Errorf("ntlm : invalid username %v", config.User)
|
||||
}
|
||||
domainUser := strings.SplitN(config.User, "\\", 2)
|
||||
return &Auth{
|
||||
Domain: domainUser[0],
|
||||
UserName: domainUser[1],
|
||||
Password: config.Password,
|
||||
Workstation: config.Workstation,
|
||||
ChannelBinding: []byte{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func utf16le(val string) []byte {
|
||||
var v []byte
|
||||
for _, r := range val {
|
||||
if utf16.IsSurrogate(r) {
|
||||
r1, r2 := utf16.EncodeRune(r)
|
||||
v = append(v, byte(r1), byte(r1>>8))
|
||||
v = append(v, byte(r2), byte(r2>>8))
|
||||
} else {
|
||||
v = append(v, byte(r), byte(r>>8))
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (auth *Auth) InitialBytes() ([]byte, error) {
|
||||
domain_len := len(auth.Domain)
|
||||
workstation_len := len(auth.Workstation)
|
||||
msg := make([]byte, 40+domain_len+workstation_len)
|
||||
copy(msg, []byte("NTLMSSP\x00"))
|
||||
binary.LittleEndian.PutUint32(msg[8:], _NEGOTIATE_MESSAGE)
|
||||
binary.LittleEndian.PutUint32(msg[12:], _NEGOTIATE_FLAGS)
|
||||
// Domain Name Fields
|
||||
binary.LittleEndian.PutUint16(msg[16:], uint16(domain_len))
|
||||
binary.LittleEndian.PutUint16(msg[18:], uint16(domain_len))
|
||||
binary.LittleEndian.PutUint32(msg[20:], 40)
|
||||
// Workstation Fields
|
||||
binary.LittleEndian.PutUint16(msg[24:], uint16(workstation_len))
|
||||
binary.LittleEndian.PutUint16(msg[26:], uint16(workstation_len))
|
||||
binary.LittleEndian.PutUint32(msg[28:], uint32(40+domain_len))
|
||||
// Version
|
||||
binary.LittleEndian.PutUint32(msg[32:], 0)
|
||||
binary.LittleEndian.PutUint32(msg[36:], 0)
|
||||
// Payload
|
||||
copy(msg[40:], auth.Domain)
|
||||
copy(msg[40+domain_len:], auth.Workstation)
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
var errorNTLM = errors.New("NTLM protocol error")
|
||||
|
||||
func createDesKey(bytes, material []byte) {
|
||||
material[0] = bytes[0]
|
||||
material[1] = (byte)(bytes[0]<<7 | (bytes[1]&0xff)>>1)
|
||||
material[2] = (byte)(bytes[1]<<6 | (bytes[2]&0xff)>>2)
|
||||
material[3] = (byte)(bytes[2]<<5 | (bytes[3]&0xff)>>3)
|
||||
material[4] = (byte)(bytes[3]<<4 | (bytes[4]&0xff)>>4)
|
||||
material[5] = (byte)(bytes[4]<<3 | (bytes[5]&0xff)>>5)
|
||||
material[6] = (byte)(bytes[5]<<2 | (bytes[6]&0xff)>>6)
|
||||
material[7] = (byte)(bytes[6] << 1)
|
||||
}
|
||||
|
||||
func encryptDes(key []byte, cleartext []byte, ciphertext []byte) {
|
||||
var desKey [8]byte
|
||||
createDesKey(key, desKey[:])
|
||||
cipher, err := des.NewCipher(desKey[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cipher.Encrypt(ciphertext, cleartext)
|
||||
}
|
||||
|
||||
func response(challenge [8]byte, hash [21]byte) (ret [24]byte) {
|
||||
encryptDes(hash[:7], challenge[:], ret[:8])
|
||||
encryptDes(hash[7:14], challenge[:], ret[8:16])
|
||||
encryptDes(hash[14:], challenge[:], ret[16:])
|
||||
return
|
||||
}
|
||||
|
||||
func lmHash(password string) (hash [21]byte) {
|
||||
var lmpass [14]byte
|
||||
copy(lmpass[:14], []byte(strings.ToUpper(password)))
|
||||
magic := []byte("KGS!@#$%")
|
||||
encryptDes(lmpass[:7], magic, hash[:8])
|
||||
encryptDes(lmpass[7:], magic, hash[8:])
|
||||
return
|
||||
}
|
||||
|
||||
func lmResponse(challenge [8]byte, password string) [24]byte {
|
||||
hash := lmHash(password)
|
||||
return response(challenge, hash)
|
||||
}
|
||||
|
||||
func ntlmHash(password string) (hash [21]byte) {
|
||||
h := md4.New()
|
||||
h.Write(utf16le(password))
|
||||
h.Sum(hash[:0])
|
||||
return
|
||||
}
|
||||
|
||||
func ntResponse(challenge [8]byte, password string) [24]byte {
|
||||
hash := ntlmHash(password)
|
||||
return response(challenge, hash)
|
||||
}
|
||||
|
||||
func clientChallenge() (nonce [8]byte) {
|
||||
_, err := rand.Read(nonce[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ntlmSessionResponse(clientNonce [8]byte, serverChallenge [8]byte, password string) [24]byte {
|
||||
var sessionHash [16]byte
|
||||
h := md5.New()
|
||||
h.Write(serverChallenge[:])
|
||||
h.Write(clientNonce[:])
|
||||
h.Sum(sessionHash[:0])
|
||||
var hash [8]byte
|
||||
copy(hash[:], sessionHash[:8])
|
||||
passwordHash := ntlmHash(password)
|
||||
return response(hash, passwordHash)
|
||||
}
|
||||
|
||||
func ntlmHashNoPadding(val string) []byte {
|
||||
hash := make([]byte, 16)
|
||||
h := md4.New()
|
||||
h.Write(utf16le(val))
|
||||
h.Sum(hash[:0])
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
func hmacMD5(passwordHash, data []byte) []byte {
|
||||
hmacEntity := hmac.New(md5.New, passwordHash)
|
||||
hmacEntity.Write(data)
|
||||
|
||||
return hmacEntity.Sum(nil)
|
||||
}
|
||||
|
||||
func getNTLMv2AndLMv2ResponsePayloads(userDomain, username, password string, challenge, nonce [8]byte, targetInfoFields []byte, timestamp time.Time) (ntlmV2Payload, lmV2Payload []byte) {
|
||||
// NTLMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response
|
||||
|
||||
ntlmHash := ntlmHashNoPadding(password)
|
||||
usernameAndTargetBytes := utf16le(strings.ToUpper(username) + userDomain)
|
||||
ntlmV2Hash := hmacMD5(ntlmHash, usernameAndTargetBytes)
|
||||
targetInfoLength := len(targetInfoFields)
|
||||
blob := make([]byte, 32+targetInfoLength)
|
||||
binary.BigEndian.PutUint32(blob[:4], 0x01010000)
|
||||
binary.BigEndian.PutUint32(blob[4:8], 0x00000000)
|
||||
binary.BigEndian.PutUint64(blob[8:16], uint64(timestamp.UnixNano()))
|
||||
copy(blob[16:24], nonce[:])
|
||||
binary.BigEndian.PutUint32(blob[24:28], 0x00000000)
|
||||
copy(blob[28:], targetInfoFields)
|
||||
binary.BigEndian.PutUint32(blob[28+targetInfoLength:], 0x00000000)
|
||||
challengeLength := len(challenge)
|
||||
blobLength := len(blob)
|
||||
challengeAndBlob := make([]byte, challengeLength+blobLength)
|
||||
copy(challengeAndBlob[:challengeLength], challenge[:])
|
||||
copy(challengeAndBlob[challengeLength:], blob)
|
||||
hashedChallenge := hmacMD5(ntlmV2Hash, challengeAndBlob)
|
||||
ntlmV2Payload = append(hashedChallenge, blob...)
|
||||
|
||||
// LMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theLmv2Response
|
||||
ntlmV2hash := hmacMD5(ntlmHash, usernameAndTargetBytes)
|
||||
challengeAndNonce := make([]byte, 16)
|
||||
copy(challengeAndNonce[:8], challenge[:])
|
||||
copy(challengeAndNonce[8:], nonce[:])
|
||||
hashedChallenge = hmacMD5(ntlmV2hash, challengeAndNonce)
|
||||
lmV2Payload = append(hashedChallenge, nonce[:]...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password, userDom string, channelBinding []byte) (lm, nt []byte, err error) {
|
||||
nonce := clientChallenge()
|
||||
|
||||
// Official specification: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4
|
||||
// Unofficial walk through referenced by https://www.freetds.org/userguide/domains.htm: http://davenport.sourceforge.net/ntlm.html
|
||||
if (flags & _NEGOTIATE_TARGET_INFO) != 0 {
|
||||
targetInfoFields, err := getNTLMv2TargetInfoFields(message)
|
||||
if err != nil {
|
||||
return lm, nt, err
|
||||
}
|
||||
|
||||
if len(channelBinding) > 0 {
|
||||
av_pair_cb := make([]byte, 4)
|
||||
// Create the AV_PAIR structure for channel bindings as specified in MS-NLMP.
|
||||
// Set AvId to MsvAvChannelBindings and AvLen to the length of the channel binding data.
|
||||
// See: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/83f5e789-660d-4781-8491-5f8c6641f75e
|
||||
binary.LittleEndian.PutUint16(av_pair_cb[0:2], AV_PAIR_MsvAvChannelBindings)
|
||||
binary.LittleEndian.PutUint16(av_pair_cb[2:4], uint16(len(channelBinding)))
|
||||
av_pair_cb = append(av_pair_cb, channelBinding...)
|
||||
|
||||
targetInfoFields = append(targetInfoFields[:len(targetInfoFields)-4], av_pair_cb...)
|
||||
targetInfoFields = append(targetInfoFields, 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
nt, lm = getNTLMv2AndLMv2ResponsePayloads(userDom, username, password, challenge, nonce, targetInfoFields, time.Now())
|
||||
|
||||
return lm, nt, nil
|
||||
}
|
||||
|
||||
var lm_bytes [24]byte
|
||||
copy(lm_bytes[:8], nonce[:])
|
||||
lm = lm_bytes[:]
|
||||
nt_bytes := ntlmSessionResponse(nonce, challenge, password)
|
||||
nt = nt_bytes[:]
|
||||
|
||||
return lm, nt, nil
|
||||
}
|
||||
|
||||
func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, err error) {
|
||||
type2MessageError := "mssql: while parsing NTLMv2 type 2 message, length %d too small for offset %d"
|
||||
type2MessageLength := len(type2Message)
|
||||
if type2MessageLength < 20 {
|
||||
return nil, fmt.Errorf(type2MessageError, type2MessageLength, 20)
|
||||
}
|
||||
|
||||
targetNameAllocated := binary.LittleEndian.Uint16(type2Message[14:16])
|
||||
targetNameOffset := binary.LittleEndian.Uint32(type2Message[16:20])
|
||||
endOfOffset := int(targetNameOffset + uint32(targetNameAllocated))
|
||||
if type2MessageLength < endOfOffset {
|
||||
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
|
||||
}
|
||||
|
||||
targetInformationAllocated := binary.LittleEndian.Uint16(type2Message[42:44])
|
||||
targetInformationDataOffset := binary.LittleEndian.Uint32(type2Message[44:48])
|
||||
endOfOffset = int(targetInformationDataOffset + uint32(targetInformationAllocated))
|
||||
if type2MessageLength < endOfOffset {
|
||||
return nil, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset)
|
||||
}
|
||||
|
||||
targetInformationBytes := make([]byte, targetInformationAllocated)
|
||||
copy(targetInformationBytes, type2Message[targetInformationDataOffset:targetInformationDataOffset+uint32(targetInformationAllocated)])
|
||||
|
||||
return targetInformationBytes, nil
|
||||
}
|
||||
|
||||
func buildNTLMResponsePayload(lm, nt []byte, flags uint32, domain, workstation, username string) ([]byte, error) {
|
||||
lm_len := len(lm)
|
||||
nt_len := len(nt)
|
||||
domain16 := utf16le(domain)
|
||||
domain_len := len(domain16)
|
||||
user16 := utf16le(username)
|
||||
user_len := len(user16)
|
||||
workstation16 := utf16le(workstation)
|
||||
workstation_len := len(workstation16)
|
||||
msg := make([]byte, 88+lm_len+nt_len+domain_len+user_len+workstation_len)
|
||||
copy(msg, []byte("NTLMSSP\x00"))
|
||||
binary.LittleEndian.PutUint32(msg[8:], _AUTHENTICATE_MESSAGE)
|
||||
|
||||
// Lm Challenge Response Fields
|
||||
binary.LittleEndian.PutUint16(msg[12:], uint16(lm_len))
|
||||
binary.LittleEndian.PutUint16(msg[14:], uint16(lm_len))
|
||||
binary.LittleEndian.PutUint32(msg[16:], 88)
|
||||
|
||||
// Nt Challenge Response Fields
|
||||
binary.LittleEndian.PutUint16(msg[20:], uint16(nt_len))
|
||||
binary.LittleEndian.PutUint16(msg[22:], uint16(nt_len))
|
||||
binary.LittleEndian.PutUint32(msg[24:], uint32(88+lm_len))
|
||||
|
||||
// Domain Name Fields
|
||||
binary.LittleEndian.PutUint16(msg[28:], uint16(domain_len))
|
||||
binary.LittleEndian.PutUint16(msg[30:], uint16(domain_len))
|
||||
binary.LittleEndian.PutUint32(msg[32:], uint32(88+lm_len+nt_len))
|
||||
|
||||
// User Name Fields
|
||||
binary.LittleEndian.PutUint16(msg[36:], uint16(user_len))
|
||||
binary.LittleEndian.PutUint16(msg[38:], uint16(user_len))
|
||||
binary.LittleEndian.PutUint32(msg[40:], uint32(88+lm_len+nt_len+domain_len))
|
||||
|
||||
// Workstation Fields
|
||||
binary.LittleEndian.PutUint16(msg[44:], uint16(workstation_len))
|
||||
binary.LittleEndian.PutUint16(msg[46:], uint16(workstation_len))
|
||||
binary.LittleEndian.PutUint32(msg[48:], uint32(88+lm_len+nt_len+domain_len+user_len))
|
||||
|
||||
// Encrypted Random Session Key Fields
|
||||
binary.LittleEndian.PutUint16(msg[52:], 0)
|
||||
binary.LittleEndian.PutUint16(msg[54:], 0)
|
||||
binary.LittleEndian.PutUint32(msg[56:], uint32(88+lm_len+nt_len+domain_len+user_len+workstation_len))
|
||||
|
||||
// Negotiate Flags
|
||||
binary.LittleEndian.PutUint32(msg[60:], flags)
|
||||
|
||||
// Version
|
||||
binary.LittleEndian.PutUint32(msg[64:], 0)
|
||||
binary.LittleEndian.PutUint32(msg[68:], 0)
|
||||
|
||||
// MIC
|
||||
binary.LittleEndian.PutUint32(msg[72:], 0)
|
||||
binary.LittleEndian.PutUint32(msg[76:], 0)
|
||||
binary.LittleEndian.PutUint32(msg[88:], 0)
|
||||
binary.LittleEndian.PutUint32(msg[84:], 0)
|
||||
|
||||
// Payload
|
||||
copy(msg[88:], lm)
|
||||
copy(msg[88+lm_len:], nt)
|
||||
copy(msg[88+lm_len+nt_len:], domain16)
|
||||
copy(msg[88+lm_len+nt_len+domain_len:], user16)
|
||||
copy(msg[88+lm_len+nt_len+domain_len+user_len:], workstation16)
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (auth *Auth) NextBytes(bytes []byte) ([]byte, error) {
|
||||
signature := string(bytes[0:8])
|
||||
if signature != "NTLMSSP\x00" {
|
||||
return nil, errorNTLM
|
||||
}
|
||||
|
||||
messageTypeIndicator := binary.LittleEndian.Uint32(bytes[8:12])
|
||||
if messageTypeIndicator != _CHALLENGE_MESSAGE {
|
||||
return nil, errorNTLM
|
||||
}
|
||||
|
||||
var challenge [8]byte
|
||||
copy(challenge[:], bytes[24:32])
|
||||
flags := binary.LittleEndian.Uint32(bytes[20:24])
|
||||
if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 {
|
||||
lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password, auth.Domain, auth.ChannelBinding)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName)
|
||||
}
|
||||
|
||||
lm_bytes := lmResponse(challenge, auth.Password)
|
||||
lm := lm_bytes[:]
|
||||
nt_bytes := ntResponse(challenge, auth.Password)
|
||||
nt := nt_bytes[:]
|
||||
|
||||
return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName)
|
||||
}
|
||||
|
||||
func (auth *Auth) Free() {
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package ntlm
|
||||
|
||||
import (
|
||||
"github.com/microsoft/go-mssqldb/integratedauth"
|
||||
)
|
||||
|
||||
// AuthProvider handles NTLM SSPI Windows Authentication
|
||||
var AuthProvider integratedauth.Provider = integratedauth.ProviderFunc(getAuth)
|
||||
|
||||
func init() {
|
||||
err := integratedauth.SetIntegratedAuthenticationProvider("ntlm", AuthProvider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winsspi
|
||||
|
||||
import "github.com/microsoft/go-mssqldb/integratedauth"
|
||||
|
||||
// AuthProvider handles SSPI Windows Authentication via secur32.dll functions
|
||||
var AuthProvider integratedauth.Provider = integratedauth.ProviderFunc(getAuth)
|
||||
|
||||
func init() {
|
||||
err := integratedauth.SetIntegratedAuthenticationProvider("winsspi", AuthProvider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winsspi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/microsoft/go-mssqldb/integratedauth"
|
||||
"github.com/microsoft/go-mssqldb/msdsn"
|
||||
)
|
||||
|
||||
var (
|
||||
secur32_dll = syscall.NewLazyDLL("secur32.dll")
|
||||
initSecurityInterface = secur32_dll.NewProc("InitSecurityInterfaceW")
|
||||
sec_fn *SecurityFunctionTable
|
||||
)
|
||||
|
||||
func init() {
|
||||
ptr, _, _ := initSecurityInterface.Call()
|
||||
sec_fn = (*SecurityFunctionTable)(unsafe.Pointer(ptr))
|
||||
}
|
||||
|
||||
const (
|
||||
SEC_E_OK = 0
|
||||
SECPKG_CRED_OUTBOUND = 2
|
||||
SECPKG_ATTR_UNIQUE_BINDINGS = 25
|
||||
SEC_WINNT_AUTH_IDENTITY_UNICODE = 2
|
||||
ISC_REQ_DELEGATE = 0x00000001
|
||||
ISC_REQ_REPLAY_DETECT = 0x00000004
|
||||
ISC_REQ_SEQUENCE_DETECT = 0x00000008
|
||||
ISC_REQ_CONFIDENTIALITY = 0x00000010
|
||||
ISC_REQ_CONNECTION = 0x00000800
|
||||
SECURITY_NETWORK_DREP = 0
|
||||
SEC_I_CONTINUE_NEEDED = 0x00090312
|
||||
SEC_I_COMPLETE_NEEDED = 0x00090313
|
||||
SEC_I_COMPLETE_AND_CONTINUE = 0x00090314
|
||||
SECBUFFER_VERSION = 0
|
||||
SECBUFFER_TOKEN = 2
|
||||
SECBUFFER_CHANNEL_BINDINGS = 14
|
||||
NTLMBUF_LEN = 12000
|
||||
)
|
||||
|
||||
const ISC_REQ = ISC_REQ_CONFIDENTIALITY |
|
||||
ISC_REQ_REPLAY_DETECT |
|
||||
ISC_REQ_SEQUENCE_DETECT |
|
||||
ISC_REQ_CONNECTION |
|
||||
ISC_REQ_DELEGATE
|
||||
|
||||
type SecurityFunctionTable struct {
|
||||
dwVersion uint32
|
||||
EnumerateSecurityPackages uintptr
|
||||
QueryCredentialsAttributes uintptr
|
||||
AcquireCredentialsHandle uintptr
|
||||
FreeCredentialsHandle uintptr
|
||||
Reserved2 uintptr
|
||||
InitializeSecurityContext uintptr
|
||||
AcceptSecurityContext uintptr
|
||||
CompleteAuthToken uintptr
|
||||
DeleteSecurityContext uintptr
|
||||
ApplyControlToken uintptr
|
||||
QueryContextAttributes uintptr
|
||||
ImpersonateSecurityContext uintptr
|
||||
RevertSecurityContext uintptr
|
||||
MakeSignature uintptr
|
||||
VerifySignature uintptr
|
||||
FreeContextBuffer uintptr
|
||||
QuerySecurityPackageInfo uintptr
|
||||
Reserved3 uintptr
|
||||
Reserved4 uintptr
|
||||
Reserved5 uintptr
|
||||
Reserved6 uintptr
|
||||
Reserved7 uintptr
|
||||
Reserved8 uintptr
|
||||
QuerySecurityContextToken uintptr
|
||||
EncryptMessage uintptr
|
||||
DecryptMessage uintptr
|
||||
}
|
||||
|
||||
type SEC_WINNT_AUTH_IDENTITY struct {
|
||||
User *uint16
|
||||
UserLength uint32
|
||||
Domain *uint16
|
||||
DomainLength uint32
|
||||
Password *uint16
|
||||
PasswordLength uint32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
type TimeStamp struct {
|
||||
LowPart uint32
|
||||
HighPart int32
|
||||
}
|
||||
|
||||
type SecHandle struct {
|
||||
dwLower uintptr
|
||||
dwUpper uintptr
|
||||
}
|
||||
|
||||
type SecBuffer struct {
|
||||
cbBuffer uint32
|
||||
BufferType uint32
|
||||
pvBuffer *byte
|
||||
}
|
||||
|
||||
type SecBufferDesc struct {
|
||||
ulVersion uint32
|
||||
cBuffers uint32
|
||||
pBuffers *SecBuffer
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
Domain string
|
||||
UserName string
|
||||
Password string
|
||||
Service string
|
||||
cred SecHandle
|
||||
ctxt SecHandle
|
||||
channelBinding *integratedauth.SEC_CHANNEL_BINDINGS
|
||||
}
|
||||
|
||||
type SecPkgContext_Bindings struct {
|
||||
BindingsLength uint64
|
||||
Bindings *byte
|
||||
}
|
||||
|
||||
func (auth *Auth) SetChannelBinding(channelBinding *integratedauth.ChannelBindings) {
|
||||
auth.channelBinding = channelBinding.AsSSPI_SEC_CHANNEL_BINDINGS()
|
||||
}
|
||||
|
||||
// getAuth returns an authentication handle Auth to provide authentication content
|
||||
// to mssql.connect
|
||||
func getAuth(config msdsn.Config) (integratedauth.IntegratedAuthenticator, error) {
|
||||
if config.User == "" {
|
||||
return &Auth{Service: config.ServerSPN}, nil
|
||||
}
|
||||
if !strings.ContainsRune(config.User, '\\') {
|
||||
return nil, fmt.Errorf("winsspi : invalid username %v", config.User)
|
||||
}
|
||||
domainUser := strings.SplitN(config.User, "\\", 2)
|
||||
return &Auth{
|
||||
Domain: domainUser[0],
|
||||
UserName: domainUser[1],
|
||||
Password: config.Password,
|
||||
Service: config.ServerSPN,
|
||||
channelBinding: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (auth *Auth) InitialBytes() ([]byte, error) {
|
||||
var identity *SEC_WINNT_AUTH_IDENTITY
|
||||
if auth.UserName != "" {
|
||||
identity = &SEC_WINNT_AUTH_IDENTITY{
|
||||
Flags: SEC_WINNT_AUTH_IDENTITY_UNICODE,
|
||||
Password: syscall.StringToUTF16Ptr(auth.Password),
|
||||
PasswordLength: uint32(len(auth.Password)),
|
||||
Domain: syscall.StringToUTF16Ptr(auth.Domain),
|
||||
DomainLength: uint32(len(auth.Domain)),
|
||||
User: syscall.StringToUTF16Ptr(auth.UserName),
|
||||
UserLength: uint32(len(auth.UserName)),
|
||||
}
|
||||
}
|
||||
var ts TimeStamp
|
||||
sec_ok, _, _ := syscall.Syscall9(sec_fn.AcquireCredentialsHandle,
|
||||
9,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Negotiate"))),
|
||||
SECPKG_CRED_OUTBOUND,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(identity)),
|
||||
0,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&auth.cred)),
|
||||
uintptr(unsafe.Pointer(&ts)))
|
||||
if sec_ok != SEC_E_OK {
|
||||
return nil, fmt.Errorf("AcquireCredentialsHandle failed %x", sec_ok)
|
||||
}
|
||||
|
||||
var buf SecBuffer
|
||||
var desc SecBufferDesc
|
||||
desc.ulVersion = SECBUFFER_VERSION
|
||||
desc.cBuffers = 1
|
||||
desc.pBuffers = &buf
|
||||
|
||||
outbuf := make([]byte, NTLMBUF_LEN)
|
||||
buf.cbBuffer = NTLMBUF_LEN
|
||||
buf.BufferType = SECBUFFER_TOKEN
|
||||
buf.pvBuffer = &outbuf[0]
|
||||
|
||||
var attrs uint32
|
||||
sec_ok, _, _ = syscall.Syscall12(sec_fn.InitializeSecurityContext,
|
||||
12,
|
||||
uintptr(unsafe.Pointer(&auth.cred)),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(auth.Service))),
|
||||
ISC_REQ,
|
||||
0,
|
||||
SECURITY_NETWORK_DREP,
|
||||
0,
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&auth.ctxt)),
|
||||
uintptr(unsafe.Pointer(&desc)),
|
||||
uintptr(unsafe.Pointer(&attrs)),
|
||||
uintptr(unsafe.Pointer(&ts)))
|
||||
if sec_ok == SEC_I_COMPLETE_AND_CONTINUE ||
|
||||
sec_ok == SEC_I_COMPLETE_NEEDED {
|
||||
syscall.Syscall6(sec_fn.CompleteAuthToken,
|
||||
2,
|
||||
uintptr(unsafe.Pointer(&auth.ctxt)),
|
||||
uintptr(unsafe.Pointer(&desc)),
|
||||
0, 0, 0, 0)
|
||||
} else if sec_ok != SEC_E_OK &&
|
||||
sec_ok != SEC_I_CONTINUE_NEEDED {
|
||||
syscall.Syscall6(sec_fn.FreeCredentialsHandle,
|
||||
1,
|
||||
uintptr(unsafe.Pointer(&auth.cred)),
|
||||
0, 0, 0, 0, 0)
|
||||
return nil, fmt.Errorf("InitialBytes InitializeSecurityContext failed %x", sec_ok)
|
||||
}
|
||||
return outbuf[:buf.cbBuffer], nil
|
||||
}
|
||||
|
||||
func (auth *Auth) NextBytes(bytes []byte) ([]byte, error) {
|
||||
var in_buf, out_buf SecBuffer
|
||||
var in_desc, out_desc SecBufferDesc
|
||||
// Use fixed-size array instead of slice to ensure memory stability
|
||||
var in_desc_buffers [2]SecBuffer
|
||||
bufferCount := 0
|
||||
|
||||
out_desc.ulVersion = SECBUFFER_VERSION
|
||||
out_desc.cBuffers = 1
|
||||
out_desc.pBuffers = &out_buf
|
||||
|
||||
// First buffer: input token
|
||||
in_buf.BufferType = SECBUFFER_TOKEN
|
||||
in_buf.pvBuffer = &bytes[0]
|
||||
in_buf.cbBuffer = uint32(len(bytes))
|
||||
in_desc_buffers[bufferCount] = in_buf
|
||||
bufferCount++
|
||||
|
||||
// Second buffer: channel bindings (if present)
|
||||
if auth.channelBinding != nil {
|
||||
channelBindingBytes := auth.channelBinding.ToBytes()
|
||||
in_desc_buffers[bufferCount].BufferType = SECBUFFER_CHANNEL_BINDINGS
|
||||
in_desc_buffers[bufferCount].pvBuffer = &channelBindingBytes[0]
|
||||
in_desc_buffers[bufferCount].cbBuffer = uint32(len(channelBindingBytes))
|
||||
bufferCount++
|
||||
}
|
||||
|
||||
in_desc.ulVersion = SECBUFFER_VERSION
|
||||
in_desc.cBuffers = uint32(bufferCount)
|
||||
in_desc.pBuffers = &in_desc_buffers[0]
|
||||
|
||||
outbuf := make([]byte, NTLMBUF_LEN)
|
||||
out_buf.BufferType = SECBUFFER_TOKEN
|
||||
out_buf.pvBuffer = &outbuf[0]
|
||||
out_buf.cbBuffer = NTLMBUF_LEN
|
||||
|
||||
var attrs uint32
|
||||
var ts TimeStamp
|
||||
sec_ok, _, _ := syscall.Syscall12(sec_fn.InitializeSecurityContext,
|
||||
12,
|
||||
uintptr(unsafe.Pointer(&auth.cred)),
|
||||
uintptr(unsafe.Pointer(&auth.ctxt)),
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(auth.Service))),
|
||||
ISC_REQ,
|
||||
0,
|
||||
SECURITY_NETWORK_DREP,
|
||||
uintptr(unsafe.Pointer(&in_desc)),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&auth.ctxt)),
|
||||
uintptr(unsafe.Pointer(&out_desc)),
|
||||
uintptr(unsafe.Pointer(&attrs)),
|
||||
uintptr(unsafe.Pointer(&ts)))
|
||||
if sec_ok == SEC_I_COMPLETE_AND_CONTINUE ||
|
||||
sec_ok == SEC_I_COMPLETE_NEEDED {
|
||||
syscall.Syscall6(sec_fn.CompleteAuthToken,
|
||||
2,
|
||||
uintptr(unsafe.Pointer(&auth.ctxt)),
|
||||
uintptr(unsafe.Pointer(&out_desc)),
|
||||
0, 0, 0, 0)
|
||||
} else if sec_ok != SEC_E_OK &&
|
||||
sec_ok != SEC_I_CONTINUE_NEEDED {
|
||||
return nil, fmt.Errorf("NextBytes InitializeSecurityContext failed %x", sec_ok)
|
||||
}
|
||||
|
||||
return outbuf[:out_buf.cbBuffer], nil
|
||||
}
|
||||
|
||||
func (auth *Auth) Free() {
|
||||
syscall.Syscall6(sec_fn.DeleteSecurityContext,
|
||||
1,
|
||||
uintptr(unsafe.Pointer(&auth.ctxt)),
|
||||
0, 0, 0, 0, 0)
|
||||
syscall.Syscall6(sec_fn.FreeCredentialsHandle,
|
||||
1,
|
||||
uintptr(unsafe.Pointer(&auth.cred)),
|
||||
0, 0, 0, 0, 0)
|
||||
}
|
||||
Reference in New Issue
Block a user