Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 1 | package client |
| 2 | |
| 3 | import ( |
| 4 | "gopkg.in/jcmturner/gokrb5.v7/iana/flags" |
| 5 | "gopkg.in/jcmturner/gokrb5.v7/iana/nametype" |
| 6 | "gopkg.in/jcmturner/gokrb5.v7/krberror" |
| 7 | "gopkg.in/jcmturner/gokrb5.v7/messages" |
| 8 | "gopkg.in/jcmturner/gokrb5.v7/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(), kdcRealm, cl.Config, tgt, sessionKey, 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.Config.ResolveRealm(princ.NameString[len(princ.NameString)-1]) |
| 93 | |
| 94 | tgt, skey, err := cl.sessionTGT(realm) |
| 95 | if err != nil { |
| 96 | return tkt, skey, err |
| 97 | } |
| 98 | _, tgsRep, err := cl.TGSREQGenerateAndExchange(princ, realm, tgt, skey, false) |
| 99 | if err != nil { |
| 100 | return tkt, skey, err |
| 101 | } |
| 102 | return tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, nil |
| 103 | } |