1package client
2
3import (
4	"github.com/jcmturner/gokrb5/v8/crypto"
5	"github.com/jcmturner/gokrb5/v8/crypto/etype"
6	"github.com/jcmturner/gokrb5/v8/iana/errorcode"
7	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
8	"github.com/jcmturner/gokrb5/v8/iana/patype"
9	"github.com/jcmturner/gokrb5/v8/krberror"
10	"github.com/jcmturner/gokrb5/v8/messages"
11	"github.com/jcmturner/gokrb5/v8/types"
12)
13
14// ASExchange performs an AS exchange for the client to retrieve a TGT.
15func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (messages.ASRep, error) {
16	if ok, err := cl.IsConfigured(); !ok {
17		return messages.ASRep{}, krberror.Errorf(err, krberror.ConfigError, "AS Exchange cannot be performed")
18	}
19
20	// Set PAData if required
21	err := setPAData(cl, nil, &ASReq)
22	if err != nil {
23		return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: issue with setting PAData on AS_REQ")
24	}
25
26	b, err := ASReq.Marshal()
27	if err != nil {
28		return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ")
29	}
30	var ASRep messages.ASRep
31
32	rb, err := cl.sendToKDC(b, realm)
33	if err != nil {
34		if e, ok := err.(messages.KRBError); ok {
35			switch e.ErrorCode {
36			case errorcode.KDC_ERR_PREAUTH_REQUIRED, errorcode.KDC_ERR_PREAUTH_FAILED:
37				// From now on assume this client will need to do this pre-auth and set the PAData
38				cl.settings.assumePreAuthentication = true
39				err = setPAData(cl, &e, &ASReq)
40				if err != nil {
41					return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: failed setting AS_REQ PAData for pre-authentication required")
42				}
43				b, err := ASReq.Marshal()
44				if err != nil {
45					return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ with PAData")
46				}
47				rb, err = cl.sendToKDC(b, realm)
48				if err != nil {
49					if _, ok := err.(messages.KRBError); ok {
50						return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
51					}
52					return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
53				}
54			case errorcode.KDC_ERR_WRONG_REALM:
55				// Client referral https://tools.ietf.org/html/rfc6806.html#section-7
56				if referral > 5 {
57					return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of client referrals exceeded")
58				}
59				referral++
60				return cl.ASExchange(e.CRealm, ASReq, referral)
61			default:
62				return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
63			}
64		} else {
65			return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
66		}
67	}
68	err = ASRep.Unmarshal(rb)
69	if err != nil {
70		return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed to process the AS_REP")
71	}
72	if ok, err := ASRep.Verify(cl.Config, cl.Credentials, ASReq); !ok {
73		return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: AS_REP is not valid or client password/keytab incorrect")
74	}
75	return ASRep, nil
76}
77
78// setPAData adds pre-authentication data to the AS_REQ.
79func setPAData(cl *Client, krberr *messages.KRBError, ASReq *messages.ASReq) error {
80	if !cl.settings.DisablePAFXFAST() {
81		pa := types.PAData{PADataType: patype.PA_REQ_ENC_PA_REP}
82		ASReq.PAData = append(ASReq.PAData, pa)
83	}
84	if cl.settings.AssumePreAuthentication() {
85		// Identify the etype to use to encrypt the PA Data
86		var et etype.EType
87		var err error
88		var key types.EncryptionKey
89		kvno := 1
90		if krberr == nil {
91			// This is not in response to an error from the KDC. It is preemptive or renewal
92			// There is no KRB Error that tells us the etype to use
93			etn := cl.settings.preAuthEType // Use the etype that may have previously been negotiated
94			if etn == 0 {
95				etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0]) // Resort to config
96			}
97			et, err = crypto.GetEtype(etn)
98			if err != nil {
99				return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
100			}
101			key, kvno, err = cl.Key(et, 0, nil)
102			if err != nil {
103				return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
104			}
105		} else {
106			// Get the etype to use from the PA data in the KRBError e-data
107			et, err = preAuthEType(krberr)
108			if err != nil {
109				return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
110			}
111			cl.settings.preAuthEType = et.GetETypeID() // Set the etype that has been defined for potential future use
112			key, kvno, err = cl.Key(et, 0, krberr)
113			if err != nil {
114				return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
115			}
116		}
117		// Generate the PA data
118		paTSb, err := types.GetPAEncTSEncAsnMarshalled()
119		if err != nil {
120			return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication")
121		}
122		paEncTS, err := crypto.GetEncryptedData(paTSb, key, keyusage.AS_REQ_PA_ENC_TIMESTAMP, kvno)
123		if err != nil {
124			return krberror.Errorf(err, krberror.EncryptingError, "error encrypting pre-authentication timestamp")
125		}
126		pb, err := paEncTS.Marshal()
127		if err != nil {
128			return krberror.Errorf(err, krberror.EncodingError, "error marshaling the PAEncTSEnc encrypted data")
129		}
130		pa := types.PAData{
131			PADataType:  patype.PA_ENC_TIMESTAMP,
132			PADataValue: pb,
133		}
134		// Look for and delete any exiting patype.PA_ENC_TIMESTAMP
135		for i, pa := range ASReq.PAData {
136			if pa.PADataType == patype.PA_ENC_TIMESTAMP {
137				ASReq.PAData[i] = ASReq.PAData[len(ASReq.PAData)-1]
138				ASReq.PAData = ASReq.PAData[:len(ASReq.PAData)-1]
139			}
140		}
141		ASReq.PAData = append(ASReq.PAData, pa)
142	}
143	return nil
144}
145
146// preAuthEType establishes what encryption type to use for pre-authentication from the KRBError returned from the KDC.
147func preAuthEType(krberr *messages.KRBError) (etype etype.EType, err error) {
148	//The preferred ordering of the "hint" pre-authentication data that
149	//affect client key selection is: ETYPE-INFO2, followed by ETYPE-INFO,
150	//followed by PW-SALT.
151	//A KDC SHOULD NOT send PA-PW-SALT when issuing a KRB-ERROR message
152	//that requests additional pre-authentication.  Implementation note:
153	//Some KDC implementations issue an erroneous PA-PW-SALT when issuing a
154	//KRB-ERROR message that requests additional pre-authentication.
155	//Therefore, clients SHOULD ignore a PA-PW-SALT accompanying a
156	//KRB-ERROR message that requests additional pre-authentication.
157	var etypeID int32
158	var pas types.PADataSequence
159	e := pas.Unmarshal(krberr.EData)
160	if e != nil {
161		err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling KRBError data")
162		return
163	}
164	for _, pa := range pas {
165		switch pa.PADataType {
166		case patype.PA_ETYPE_INFO2:
167			info, e := pa.GetETypeInfo2()
168			if e != nil {
169				err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO2 data")
170				return
171			}
172			etypeID = info[0].EType
173			break
174		case patype.PA_ETYPE_INFO:
175			info, e := pa.GetETypeInfo()
176			if e != nil {
177				err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO data")
178				return
179			}
180			etypeID = info[0].EType
181		}
182	}
183	etype, e = crypto.GetEtype(etypeID)
184	if e != nil {
185		err = krberror.Errorf(e, krberror.EncryptingError, "error creating etype")
186		return
187	}
188	return etype, nil
189}
190