TGSExchange.go 4.9 KB

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