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  }