blob: 9d1a2f3f50f918444c4a83d1f8ee6dca3aeaa99e [file] [log] [blame]
Scott Baker611f6bd2019-10-18 13:45:19 -07001package client
2
3import (
4 "gopkg.in/jcmturner/gokrb5.v7/crypto"
5 "gopkg.in/jcmturner/gokrb5.v7/crypto/etype"
6 "gopkg.in/jcmturner/gokrb5.v7/iana/errorcode"
7 "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage"
8 "gopkg.in/jcmturner/gokrb5.v7/iana/patype"
9 "gopkg.in/jcmturner/gokrb5.v7/krberror"
10 "gopkg.in/jcmturner/gokrb5.v7/messages"
11 "gopkg.in/jcmturner/gokrb5.v7/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 if krberr == nil {
90 // This is not in response to an error from the KDC. It is preemptive or renewal
91 // There is no KRB Error that tells us the etype to use
92 etn := cl.settings.preAuthEType // Use the etype that may have previously been negotiated
93 if etn == 0 {
94 etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0]) // Resort to config
95 }
96 et, err = crypto.GetEtype(etn)
97 if err != nil {
98 return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
99 }
100 key, err = cl.Key(et, nil)
101 if err != nil {
102 return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
103 }
104 } else {
105 // Get the etype to use from the PA data in the KRBError e-data
106 et, err = preAuthEType(krberr)
107 if err != nil {
108 return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
109 }
110 cl.settings.preAuthEType = et.GetETypeID() // Set the etype that has been defined for potential future use
111 key, err = cl.Key(et, krberr)
112 if err != nil {
113 return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
114 }
115 }
116 // Generate the PA data
117 paTSb, err := types.GetPAEncTSEncAsnMarshalled()
118 if err != nil {
119 return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication")
120 }
121 //TODO (theme: KVNO from keytab) the kvno should not be hard coded to 1 as this hampers troubleshooting.
122 paEncTS, err := crypto.GetEncryptedData(paTSb, key, keyusage.AS_REQ_PA_ENC_TIMESTAMP, 1)
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}