@@ -5,6 +5,8 @@ package gssapi
5
5
6
6
import (
7
7
"bytes"
8
+ "crypto"
9
+ "crypto/x509"
8
10
"encoding/binary"
9
11
"fmt"
10
12
@@ -15,8 +17,9 @@ import (
15
17
// SSPIClient implements ldap.GSSAPIClient interface.
16
18
// Depends on secur32.dll.
17
19
type SSPIClient struct {
18
- creds * sspi.Credentials
19
- ctx * kerberos.ClientContext
20
+ creds * sspi.Credentials
21
+ ctx * kerberos.ClientContext
22
+ channelBindings []byte
20
23
}
21
24
22
25
// NewSSPIClient returns a client with credentials of the current user.
@@ -49,6 +52,26 @@ func NewSSPIClientWithUserCredentials(domain, username, password string) (*SSPIC
49
52
}, nil
50
53
}
51
54
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
+
52
75
// Close deletes any established secure context and closes the client.
53
76
func (c * SSPIClient ) Close () error {
54
77
err1 := c .DeleteSecContext ()
@@ -82,15 +105,25 @@ func (c *SSPIClient) InitSecContextWithOptions(target string, token []byte, APOp
82
105
83
106
switch token {
84
107
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
+
86
120
if err != nil {
87
121
return nil , false , err
88
122
}
89
123
c .ctx = ctx
90
124
91
125
return output , ! completed , nil
92
126
default :
93
-
94
127
completed , output , err := c .ctx .Update (token )
95
128
if err != nil {
96
129
return nil , false , err
@@ -99,7 +132,6 @@ func (c *SSPIClient) InitSecContextWithOptions(target string, token []byte, APOp
99
132
return nil , false , fmt .Errorf ("error verifying flags: %v" , err )
100
133
}
101
134
return output , ! completed , nil
102
-
103
135
}
104
136
}
105
137
@@ -196,3 +228,61 @@ func handshakePayload(secLayer byte, maxSize uint32, authzid []byte) []byte {
196
228
197
229
return payload
198
230
}
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