chore: ⬆️ updated deps
This commit is contained in:
+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...),
|
||||
}
|
||||
}
|
||||
+37
-10
@@ -57,11 +57,24 @@ const _NEGOTIATE_FLAGS = _NEGOTIATE_UNICODE |
|
||||
_NEGOTIATE_ALWAYS_SIGN |
|
||||
_NEGOTIATE_EXTENDED_SESSIONSECURITY
|
||||
|
||||
const (
|
||||
AV_PAIR_MsvAvChannelBindings = 0x000A
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
Domain string
|
||||
UserName string
|
||||
Password string
|
||||
Workstation string
|
||||
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
|
||||
@@ -72,10 +85,11 @@ func getAuth(config msdsn.Config) (integratedauth.IntegratedAuthenticator, error
|
||||
}
|
||||
domainUser := strings.SplitN(config.User, "\\", 2)
|
||||
return &Auth{
|
||||
Domain: domainUser[0],
|
||||
UserName: domainUser[1],
|
||||
Password: config.Password,
|
||||
Workstation: config.Workstation,
|
||||
Domain: domainUser[0],
|
||||
UserName: domainUser[1],
|
||||
Password: config.Password,
|
||||
Workstation: config.Workstation,
|
||||
ChannelBinding: []byte{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -243,7 +257,7 @@ func getNTLMv2AndLMv2ResponsePayloads(userDomain, username, password string, cha
|
||||
return
|
||||
}
|
||||
|
||||
func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password, userDom string) (lm, nt []byte, err error) {
|
||||
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
|
||||
@@ -254,6 +268,19 @@ func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8
|
||||
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
|
||||
@@ -376,7 +403,7 @@ func (auth *Auth) NextBytes(bytes []byte) ([]byte, error) {
|
||||
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)
|
||||
lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password, auth.Domain, auth.ChannelBinding)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winsspi
|
||||
@@ -12,4 +13,4 @@ func init() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+43
-14
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winsspi
|
||||
@@ -26,6 +27,7 @@ func init() {
|
||||
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
|
||||
@@ -38,6 +40,7 @@ const (
|
||||
SEC_I_COMPLETE_AND_CONTINUE = 0x00090314
|
||||
SECBUFFER_VERSION = 0
|
||||
SECBUFFER_TOKEN = 2
|
||||
SECBUFFER_CHANNEL_BINDINGS = 14
|
||||
NTLMBUF_LEN = 12000
|
||||
)
|
||||
|
||||
@@ -110,12 +113,22 @@ type SecBufferDesc struct {
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
Domain string
|
||||
UserName string
|
||||
Password string
|
||||
Service string
|
||||
cred SecHandle
|
||||
ctxt SecHandle
|
||||
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
|
||||
@@ -129,10 +142,11 @@ func getAuth(config msdsn.Config) (integratedauth.IntegratedAuthenticator, error
|
||||
}
|
||||
domainUser := strings.SplitN(config.User, "\\", 2)
|
||||
return &Auth{
|
||||
Domain: domainUser[0],
|
||||
UserName: domainUser[1],
|
||||
Password: config.Password,
|
||||
Service: config.ServerSPN,
|
||||
Domain: domainUser[0],
|
||||
UserName: domainUser[1],
|
||||
Password: config.Password,
|
||||
Service: config.ServerSPN,
|
||||
channelBinding: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -212,18 +226,33 @@ func (auth *Auth) InitialBytes() ([]byte, error) {
|
||||
func (auth *Auth) NextBytes(bytes []byte) ([]byte, error) {
|
||||
var in_buf, out_buf SecBuffer
|
||||
var in_desc, out_desc SecBufferDesc
|
||||
|
||||
in_desc.ulVersion = SECBUFFER_VERSION
|
||||
in_desc.cBuffers = 1
|
||||
in_desc.pBuffers = &in_buf
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user