github.com/jcmturner/gokrb5/v8@v8.4.4/client/TGSExchange.go (about) 1 package client 2 3 import ( 4 "github.com/jcmturner/gokrb5/v8/iana/flags" 5 "github.com/jcmturner/gokrb5/v8/iana/nametype" 6 "github.com/jcmturner/gokrb5/v8/krberror" 7 "github.com/jcmturner/gokrb5/v8/messages" 8 "github.com/jcmturner/gokrb5/v8/types" 9 ) 10 11 // TGSREQGenerateAndExchange generates the TGS_REQ and performs a TGS exchange to retrieve a ticket to the specified SPN. 12 func (cl *Client) TGSREQGenerateAndExchange(spn types.PrincipalName, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, renewal bool) (tgsReq messages.TGSReq, tgsRep messages.TGSRep, err error) { 13 tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, spn, renewal) 14 if err != nil { 15 return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: failed to generate a new TGS_REQ") 16 } 17 return cl.TGSExchange(tgsReq, kdcRealm, tgsRep.Ticket, sessionKey, 0) 18 } 19 20 // TGSExchange exchanges the provided TGS_REQ with the KDC to retrieve a TGS_REP. 21 // Referrals are automatically handled. 22 // The client's cache is updated with the ticket received. 23 func (cl *Client) TGSExchange(tgsReq messages.TGSReq, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, referral int) (messages.TGSReq, messages.TGSRep, error) { 24 var tgsRep messages.TGSRep 25 b, err := tgsReq.Marshal() 26 if err != nil { 27 return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to marshal TGS_REQ") 28 } 29 r, err := cl.sendToKDC(b, kdcRealm) 30 if err != nil { 31 if _, ok := err.(messages.KRBError); ok { 32 return tgsReq, tgsRep, krberror.Errorf(err, krberror.KDCError, "TGS Exchange Error: kerberos error response from KDC when requesting for %s", tgsReq.ReqBody.SName.PrincipalNameString()) 33 } 34 return tgsReq, tgsRep, krberror.Errorf(err, krberror.NetworkingError, "TGS Exchange Error: issue sending TGS_REQ to KDC") 35 } 36 err = tgsRep.Unmarshal(r) 37 if err != nil { 38 return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP") 39 } 40 err = tgsRep.DecryptEncPart(sessionKey) 41 if err != nil { 42 return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP") 43 } 44 if ok, err := tgsRep.Verify(cl.Config, tgsReq); !ok { 45 return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: TGS_REP is not valid") 46 } 47 48 if tgsRep.Ticket.SName.NameString[0] == "krbtgt" && !tgsRep.Ticket.SName.Equal(tgsReq.ReqBody.SName) { 49 if referral > 5 { 50 return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: maximum number of referrals exceeded") 51 } 52 // Server referral https://tools.ietf.org/html/rfc6806.html#section-8 53 // The TGS Rep contains a TGT for another domain as the service resides in that domain. 54 cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart) 55 realm := tgsRep.Ticket.SName.NameString[len(tgsRep.Ticket.SName.NameString)-1] 56 referral++ 57 if types.IsFlagSet(&tgsReq.ReqBody.KDCOptions, flags.EncTktInSkey) && len(tgsReq.ReqBody.AdditionalTickets) > 0 { 58 tgsReq, err = messages.NewUser2UserTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal, tgsReq.ReqBody.AdditionalTickets[0]) 59 if err != nil { 60 return tgsReq, tgsRep, err 61 } 62 } 63 tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), realm, cl.Config, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, tgsReq.ReqBody.SName, tgsReq.Renewal) 64 if err != nil { 65 return tgsReq, tgsRep, err 66 } 67 return cl.TGSExchange(tgsReq, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, referral) 68 } 69 cl.cache.addEntry( 70 tgsRep.Ticket, 71 tgsRep.DecryptedEncPart.AuthTime, 72 tgsRep.DecryptedEncPart.StartTime, 73 tgsRep.DecryptedEncPart.EndTime, 74 tgsRep.DecryptedEncPart.RenewTill, 75 tgsRep.DecryptedEncPart.Key, 76 ) 77 cl.Log("ticket added to cache for %s (EndTime: %v)", tgsRep.Ticket.SName.PrincipalNameString(), tgsRep.DecryptedEncPart.EndTime) 78 return tgsReq, tgsRep, err 79 } 80 81 // GetServiceTicket makes a request to get a service ticket for the SPN specified 82 // SPN format: <SERVICE>/<FQDN> Eg. HTTP/www.example.com 83 // The ticket will be added to the client's ticket cache 84 func (cl *Client) GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error) { 85 var tkt messages.Ticket 86 var skey types.EncryptionKey 87 if tkt, skey, ok := cl.GetCachedTicket(spn); ok { 88 // Already a valid ticket in the cache 89 return tkt, skey, nil 90 } 91 princ := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn) 92 realm := cl.spnRealm(princ) 93 94 // if we don't know the SPN's realm, ask the client realm's KDC 95 if realm == "" { 96 realm = cl.Credentials.Realm() 97 } 98 99 tgt, skey, err := cl.sessionTGT(realm) 100 if err != nil { 101 return tkt, skey, err 102 } 103 _, tgsRep, err := cl.TGSREQGenerateAndExchange(princ, realm, tgt, skey, false) 104 if err != nil { 105 return tkt, skey, err 106 } 107 return tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, nil 108 }