@@ -5,6 +5,8 @@ package gssapi
55
66import (
77 "bytes"
8+ "crypto"
9+ "crypto/x509"
810 "encoding/binary"
911 "fmt"
1012
@@ -15,8 +17,9 @@ import (
1517// SSPIClient implements ldap.GSSAPIClient interface.
1618// Depends on secur32.dll.
1719type SSPIClient struct {
18- creds * sspi.Credentials
19- ctx * kerberos.ClientContext
20+ creds * sspi.Credentials
21+ ctx * kerberos.ClientContext
22+ channelBindings []byte
2023}
2124
2225// NewSSPIClient returns a client with credentials of the current user.
@@ -49,6 +52,26 @@ func NewSSPIClientWithUserCredentials(domain, username, password string) (*SSPIC
4952 }, nil
5053}
5154
55+ // NewSSPIClientWithChannelBinding creates an RFC 5929 compliant client.
56+ func NewSSPIClientWithChannelBinding (cert * x509.Certificate ) (* SSPIClient , error ) {
57+ creds , err := kerberos .AcquireCurrentUserCredentials ()
58+ if err != nil {
59+ return nil , err
60+ }
61+
62+ certHash := calculateCertificateHash (cert )
63+ if certHash == nil {
64+ return nil , fmt .Errorf ("failed to calculate certificate hash" )
65+ }
66+
67+ tlsChannelBinding := append ([]byte ("tls-server-end-point:" ), certHash ... )
68+
69+ return & SSPIClient {
70+ creds : creds ,
71+ channelBindings : createChannelBindingsStructure (tlsChannelBinding ),
72+ }, nil
73+ }
74+
5275// Close deletes any established secure context and closes the client.
5376func (c * SSPIClient ) Close () error {
5477 err1 := c .DeleteSecContext ()
@@ -82,15 +105,25 @@ func (c *SSPIClient) InitSecContextWithOptions(target string, token []byte, APOp
82105
83106 switch token {
84107 case nil :
85- ctx , completed , output , err := kerberos .NewClientContextWithFlags (c .creds , target , sspiFlags )
108+ // Use channel bindings if available, otherwise fall back to the standard method.
109+ var ctx * kerberos.ClientContext
110+ var completed bool
111+ var output []byte
112+ var err error
113+
114+ if len (c .channelBindings ) > 0 {
115+ ctx , completed , output , err = kerberos .NewClientContextWithChannelBindings (c .creds , target , sspiFlags , c .channelBindings )
116+ } else {
117+ ctx , completed , output , err = kerberos .NewClientContextWithFlags (c .creds , target , sspiFlags )
118+ }
119+
86120 if err != nil {
87121 return nil , false , err
88122 }
89123 c .ctx = ctx
90124
91125 return output , ! completed , nil
92126 default :
93-
94127 completed , output , err := c .ctx .Update (token )
95128 if err != nil {
96129 return nil , false , err
@@ -99,7 +132,6 @@ func (c *SSPIClient) InitSecContextWithOptions(target string, token []byte, APOp
99132 return nil , false , fmt .Errorf ("error verifying flags: %v" , err )
100133 }
101134 return output , ! completed , nil
102-
103135 }
104136}
105137
@@ -196,3 +228,61 @@ func handshakePayload(secLayer byte, maxSize uint32, authzid []byte) []byte {
196228
197229 return payload
198230}
231+
232+ // createChannelBindingsStructure creates a Windows SEC_CHANNEL_BINDINGS structure.
233+ // This is the format that Windows SSPI expects for channel binding tokens.
234+ // https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-sec_channel_bindings
235+ func createChannelBindingsStructure (applicationData []byte ) []byte {
236+ const headerSize = 32 // 8 DWORDs * 4 bytes each
237+ appDataLen := uint32 (len (applicationData ))
238+ appDataOffset := uint32 (headerSize )
239+
240+ buf := make ([]byte , headerSize + len (applicationData ))
241+
242+ // All initiator and acceptor fields are 0 for TLS channel binding.
243+ binary .LittleEndian .PutUint32 (buf [24 :], appDataLen ) // cbApplicationDataLength
244+ binary .LittleEndian .PutUint32 (buf [28 :], appDataOffset ) // dwApplicationDataOffset
245+
246+ copy (buf [headerSize :], applicationData )
247+
248+ return buf
249+ }
250+
251+ // calculateCertificateHash implements RFC 5929 certificate hash calculation.
252+ // https://www.rfc-editor.org/rfc/rfc5929.html#section-4.1
253+ func calculateCertificateHash (cert * x509.Certificate ) []byte {
254+ var hashFunc crypto.Hash
255+
256+ switch cert .SignatureAlgorithm {
257+ case x509 .SHA256WithRSA ,
258+ x509 .SHA256WithRSAPSS ,
259+ x509 .ECDSAWithSHA256 ,
260+ x509 .DSAWithSHA256 :
261+
262+ hashFunc = crypto .SHA256
263+ case x509 .SHA384WithRSA ,
264+ x509 .SHA384WithRSAPSS ,
265+ x509 .ECDSAWithSHA384 :
266+
267+ hashFunc = crypto .SHA384
268+ case x509 .SHA512WithRSA ,
269+ x509 .SHA512WithRSAPSS ,
270+ x509 .ECDSAWithSHA512 :
271+
272+ hashFunc = crypto .SHA512
273+ case x509 .MD5WithRSA ,
274+ x509 .SHA1WithRSA ,
275+ x509 .ECDSAWithSHA1 ,
276+ x509 .DSAWithSHA1 :
277+
278+ hashFunc = crypto .SHA256
279+ default :
280+ return nil
281+ }
282+
283+ hasher := hashFunc .New ()
284+
285+ // Important to hash cert in DER format.
286+ hasher .Write (cert .Raw )
287+ return hasher .Sum (nil )
288+ }
0 commit comments