blob: e4571ce8669157ab1bb95b41b0b0cc50d65f9710 [file] [log] [blame]
khenaidoo106c61a2021-08-11 18:05:46 -04001package client
2
3import (
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.
12func (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.
23func (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
84func (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}