88 "strings"
99 "time"
1010
11+ "github.com/TheManticoreProject/Manticore/network/kerberos/v5/credentials"
1112 kerbcrypto "github.com/TheManticoreProject/Manticore/network/kerberos/v5/crypto"
1213 "github.com/TheManticoreProject/Manticore/network/kerberos/v5/messages"
1314)
@@ -29,13 +30,14 @@ type KerberosClient struct {
2930 realm string
3031 kdcHost string
3132
32- password string
33+ cred * credentials. Credential
3334
3435 // Populated after a successful GetTGT call.
3536 tgtTicket messages.Ticket
3637 tgtTicketRaw []byte // raw APPLICATION[1] bytes as received from KDC
3738 sessionKey []byte
3839 sessionEType int
40+ tgtEnc messages.EncASRepPart // decrypted AS-REP enc-part: times, flags, sname
3941 hasTGT bool
4042}
4143
@@ -50,17 +52,40 @@ func NewClient(username, realm, kdcHost string) *KerberosClient {
5052 }
5153}
5254
53- // WithPassword stores the password for use in GetTGT.
55+ // WithPassword configures a password credential for GetTGT.
5456// Returns the client to allow fluent chaining.
5557func (c * KerberosClient ) WithPassword (password string ) * KerberosClient {
56- c .password = password
58+ c .cred = credentials . NewWithPassword ( c . username , c . realm , password )
5759 return c
5860}
5961
60- // WithCCache is not yet supported in the native implementation.
61- // Use the gokrb5-backed KerberosInit helper for ccache-based LDAP binds.
62- func (c * KerberosClient ) WithCCache (_ string ) error {
63- return fmt .Errorf ("kerberos: ccache not supported in native implementation; use gokrb5 KerberosInit for LDAP GSSAPI binds" )
62+ // WithCredential configures an arbitrary credential (password, NT hash, or AES
63+ // key). Returns the client to allow fluent chaining.
64+ func (c * KerberosClient ) WithCredential (cred * credentials.Credential ) * KerberosClient {
65+ c .cred = cred
66+ return c
67+ }
68+
69+ // WithNTHash configures an NT-hash (overpass-the-hash) credential from a hex
70+ // string (accepts an "LM:NT" pair). GetTGT will request an RC4-HMAC TGT.
71+ func (c * KerberosClient ) WithNTHash (hexHash string ) error {
72+ cred , err := credentials .NewWithHexNTHash (c .username , c .realm , hexHash )
73+ if err != nil {
74+ return err
75+ }
76+ c .cred = cred
77+ return nil
78+ }
79+
80+ // WithAESKey configures a pass-the-key credential from a hex-encoded AES key
81+ // (16 bytes -> AES128, 32 bytes -> AES256).
82+ func (c * KerberosClient ) WithAESKey (hexKey string ) error {
83+ cred , err := credentials .NewWithHexAESKey (c .username , c .realm , hexKey )
84+ if err != nil {
85+ return err
86+ }
87+ c .cred = cred
88+ return nil
6489}
6590
6691// GetTGT requests a Ticket Granting Ticket from the KDC using the password
@@ -71,14 +96,14 @@ func (c *KerberosClient) WithCCache(_ string) error {
7196// (realm+username). If the KDC returns PREAUTH_REQUIRED with different
7297// etype/salt info, we retry once with the corrected values.
7398func (c * KerberosClient ) GetTGT () error {
74- if c .password == "" {
75- return fmt .Errorf ("kerberos: no credentials configured: call WithPassword first" )
99+ if c .cred == nil {
100+ return fmt .Errorf ("kerberos: no credentials configured: call WithPassword/WithNTHash/WithAESKey/WithCredential first" )
76101 }
77102
78- // Attempt with default AD salt. The KDC may respond with PREAUTH_REQUIRED
79- // if a different salt or etype is required .
80- etype := messages . ETypeAES256CTSHMACSHA196
81- salt := c .realm + c . username
103+ // Start with the strongest etype the credential can satisfy and the AD
104+ // default salt. The KDC corrects both via PREAUTH_REQUIRED if needed .
105+ etype := c . cred . SupportedETypes ()[ 0 ]
106+ salt := c .cred . DefaultSalt ()
82107 resp , nonce , err := c .sendASReqWithPreauth (etype , salt , nil )
83108 if err != nil {
84109 return err
@@ -218,7 +243,9 @@ func (c *KerberosClient) Destroy() {
218243 c .sessionKey [i ] = 0
219244 }
220245 c .sessionKey = nil
221- c .password = ""
246+ if c .cred != nil {
247+ c .cred .Destroy ()
248+ }
222249 c .hasTGT = false
223250}
224251
@@ -256,11 +283,7 @@ func (c *KerberosClient) sendASReq(pa_data []messages.PAData) ([]byte, int, erro
256283 },
257284 Till : time .Now ().UTC ().Add (24 * time .Hour ),
258285 Nonce : nonce ,
259- EType : []int {
260- messages .ETypeAES256CTSHMACSHA196 ,
261- messages .ETypeAES128CTSHMACSHA196 ,
262- messages .ETypeRC4HMAC ,
263- },
286+ EType : c .cred .SupportedETypes (),
264287 },
265288 }
266289
@@ -279,8 +302,9 @@ func (c *KerberosClient) sendASReq(pa_data []messages.PAData) ([]byte, int, erro
279302// PA-ETYPE-INFO2 structure embedded in a KRBError's EData.
280303// Falls back to AES-256 with the default AD salt if no EData is present.
281304func (c * KerberosClient ) pickETypeFromError (krb_err messages.KRBError ) (int , string , []byte ) {
282- default_salt := c .realm + c .username
283- default_etype := messages .ETypeAES256CTSHMACSHA196
305+ preferred := c .cred .SupportedETypes ()
306+ default_salt := c .cred .DefaultSalt ()
307+ default_etype := preferred [0 ]
284308
285309 if len (krb_err .EData ) == 0 {
286310 return default_etype , default_salt , nil
@@ -294,7 +318,7 @@ func (c *KerberosClient) pickETypeFromError(krb_err messages.KRBError) (int, str
294318 if pa .PADataType == messages .PAETypeInfo2 {
295319 var info messages.ETypeInfo2
296320 if _ , err := info .Unmarshal (pa .PADataValue ); err == nil && len (info ) > 0 {
297- return pickBestEType (info , default_salt )
321+ return pickBestEType (info , preferred , default_salt )
298322 }
299323 }
300324 }
@@ -303,20 +327,17 @@ func (c *KerberosClient) pickETypeFromError(krb_err messages.KRBError) (int, str
303327 // Try to parse EData directly as ETYPE-INFO2.
304328 var info messages.ETypeInfo2
305329 if _ , err := info .Unmarshal (krb_err .EData ); err == nil && len (info ) > 0 {
306- return pickBestEType (info , default_salt )
330+ return pickBestEType (info , preferred , default_salt )
307331 }
308332
309333 return default_etype , default_salt , nil
310334}
311335
312- // pickBestEType selects the strongest supported etype from an ETypeInfo2 list.
313- func pickBestEType (info messages.ETypeInfo2 , default_salt string ) (int , string , []byte ) {
314- // Preference order: AES256 > AES128 > RC4.
315- preferred := []int {
316- messages .ETypeAES256CTSHMACSHA196 ,
317- messages .ETypeAES128CTSHMACSHA196 ,
318- messages .ETypeRC4HMAC ,
319- }
336+ // pickBestEType selects, from an ETYPE-INFO2 list, the strongest etype the
337+ // credential can actually satisfy (preferred is the credential's supported set
338+ // in strength order). This prevents choosing, say, AES when only an NT hash is
339+ // held.
340+ func pickBestEType (info messages.ETypeInfo2 , preferred []int , default_salt string ) (int , string , []byte ) {
320341 for _ , want := range preferred {
321342 for _ , entry := range info {
322343 if entry .EType == want {
@@ -328,20 +349,17 @@ func pickBestEType(info messages.ETypeInfo2, default_salt string) (int, string,
328349 }
329350 }
330351 }
331- // Fallback to first entry.
332- e := info [0 ]
333- if e .Salt == "" {
334- e .Salt = default_salt
335- }
336- return e .EType , e .Salt , e .S2KParams
352+ // No advertised etype is supported by the credential; fall back to the
353+ // credential's strongest etype with the default salt.
354+ return preferred [0 ], default_salt , nil
337355}
338356
339357// sendASReqWithPreauth builds and sends an AS-REQ with PA-ENC-TIMESTAMP.
340358// Returns the raw KDC response bytes and the nonce placed in the request.
341359func (c * KerberosClient ) sendASReqWithPreauth (etype int , salt string , s2k_params []byte ) ([]byte , int , error ) {
342- key , err := kerbcrypto . StringToKey (etype , c . password , salt , s2k_params )
360+ key , err := c . cred . Key (etype , salt , s2k_params )
343361 if err != nil {
344- return nil , 0 , fmt .Errorf ("kerberos: StringToKey : %w" , err )
362+ return nil , 0 , fmt .Errorf ("kerberos: derive key : %w" , err )
345363 }
346364
347365 now := time .Now ().UTC ()
@@ -396,9 +414,9 @@ func (c *KerberosClient) processASRep(resp []byte, etype int, salt string, s2k_p
396414 return fmt .Errorf ("kerberos: parse AS-REP: %w" , err )
397415 }
398416
399- key , err := kerbcrypto . StringToKey (etype , c . password , salt , s2k_params )
417+ key , err := c . cred . Key (etype , salt , s2k_params )
400418 if err != nil {
401- return fmt .Errorf ("kerberos: StringToKey for AS-REP decrypt: %w" , err )
419+ return fmt .Errorf ("kerberos: derive key for AS-REP decrypt: %w" , err )
402420 }
403421
404422 enc_plain , err := kerbcrypto .Decrypt (etype , key , kerbcrypto .KeyUsageASRepEncPart , as_rep .EncPart .Cipher )
@@ -422,6 +440,7 @@ func (c *KerberosClient) processASRep(resp []byte, etype int, salt string, s2k_p
422440 c .tgtTicketRaw = as_rep .TicketRaw
423441 c .sessionKey = enc_as_rep .Key .KeyValue
424442 c .sessionEType = enc_as_rep .Key .KeyType
443+ c .tgtEnc = enc_as_rep
425444 c .hasTGT = true
426445 return nil
427446}
0 commit comments