blob: 69df9f0f6f549e354caada3a44dcf0524827f74c [file] [log] [blame]
khenaidoo106c61a2021-08-11 18:05:46 -04001package messages
2
3// Reference: https://www.ietf.org/rfc/rfc4120.txt
4// Section: 5.4.2
5
6import (
7 "fmt"
8 "time"
9
10 "github.com/jcmturner/gofork/encoding/asn1"
11 "github.com/jcmturner/gokrb5/v8/asn1tools"
12 "github.com/jcmturner/gokrb5/v8/config"
13 "github.com/jcmturner/gokrb5/v8/credentials"
14 "github.com/jcmturner/gokrb5/v8/crypto"
15 "github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
16 "github.com/jcmturner/gokrb5/v8/iana/flags"
17 "github.com/jcmturner/gokrb5/v8/iana/keyusage"
18 "github.com/jcmturner/gokrb5/v8/iana/msgtype"
19 "github.com/jcmturner/gokrb5/v8/iana/patype"
20 "github.com/jcmturner/gokrb5/v8/krberror"
21 "github.com/jcmturner/gokrb5/v8/types"
22)
23
24type marshalKDCRep struct {
25 PVNO int `asn1:"explicit,tag:0"`
26 MsgType int `asn1:"explicit,tag:1"`
27 PAData types.PADataSequence `asn1:"explicit,optional,tag:2"`
28 CRealm string `asn1:"generalstring,explicit,tag:3"`
29 CName types.PrincipalName `asn1:"explicit,tag:4"`
30 // Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
31 Ticket asn1.RawValue `asn1:"explicit,tag:5"`
32 EncPart types.EncryptedData `asn1:"explicit,tag:6"`
33}
34
35// KDCRepFields represents the KRB_KDC_REP fields.
36type KDCRepFields struct {
37 PVNO int
38 MsgType int
39 PAData []types.PAData
40 CRealm string
41 CName types.PrincipalName
42 Ticket Ticket
43 EncPart types.EncryptedData
44 DecryptedEncPart EncKDCRepPart
45}
46
47// ASRep implements RFC 4120 KRB_AS_REP: https://tools.ietf.org/html/rfc4120#section-5.4.2.
48type ASRep struct {
49 KDCRepFields
50}
51
52// TGSRep implements RFC 4120 KRB_TGS_REP: https://tools.ietf.org/html/rfc4120#section-5.4.2.
53type TGSRep struct {
54 KDCRepFields
55}
56
57// EncKDCRepPart is the encrypted part of KRB_KDC_REP.
58type EncKDCRepPart struct {
59 Key types.EncryptionKey `asn1:"explicit,tag:0"`
60 LastReqs []LastReq `asn1:"explicit,tag:1"`
61 Nonce int `asn1:"explicit,tag:2"`
62 KeyExpiration time.Time `asn1:"generalized,explicit,optional,tag:3"`
63 Flags asn1.BitString `asn1:"explicit,tag:4"`
64 AuthTime time.Time `asn1:"generalized,explicit,tag:5"`
65 StartTime time.Time `asn1:"generalized,explicit,optional,tag:6"`
66 EndTime time.Time `asn1:"generalized,explicit,tag:7"`
67 RenewTill time.Time `asn1:"generalized,explicit,optional,tag:8"`
68 SRealm string `asn1:"generalstring,explicit,tag:9"`
69 SName types.PrincipalName `asn1:"explicit,tag:10"`
70 CAddr []types.HostAddress `asn1:"explicit,optional,tag:11"`
71 EncPAData types.PADataSequence `asn1:"explicit,optional,tag:12"`
72}
73
74// LastReq part of KRB_KDC_REP.
75type LastReq struct {
76 LRType int32 `asn1:"explicit,tag:0"`
77 LRValue time.Time `asn1:"generalized,explicit,tag:1"`
78}
79
80// Unmarshal bytes b into the ASRep struct.
81func (k *ASRep) Unmarshal(b []byte) error {
82 var m marshalKDCRep
83 _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREP))
84 if err != nil {
85 return processUnmarshalReplyError(b, err)
86 }
87 if m.MsgType != msgtype.KRB_AS_REP {
88 return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an AS_REP. Expected: %v; Actual: %v", msgtype.KRB_AS_REP, m.MsgType)
89 }
90 //Process the raw ticket within
91 tkt, err := unmarshalTicket(m.Ticket.Bytes)
92 if err != nil {
93 return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within AS_REP")
94 }
95 k.KDCRepFields = KDCRepFields{
96 PVNO: m.PVNO,
97 MsgType: m.MsgType,
98 PAData: m.PAData,
99 CRealm: m.CRealm,
100 CName: m.CName,
101 Ticket: tkt,
102 EncPart: m.EncPart,
103 }
104 return nil
105}
106
107// Marshal ASRep struct.
108func (k *ASRep) Marshal() ([]byte, error) {
109 m := marshalKDCRep{
110 PVNO: k.PVNO,
111 MsgType: k.MsgType,
112 PAData: k.PAData,
113 CRealm: k.CRealm,
114 CName: k.CName,
115 EncPart: k.EncPart,
116 }
117 b, err := k.Ticket.Marshal()
118 if err != nil {
119 return []byte{}, err
120 }
121 m.Ticket = asn1.RawValue{
122 Class: asn1.ClassContextSpecific,
123 IsCompound: true,
124 Tag: 5,
125 Bytes: b,
126 }
127 mk, err := asn1.Marshal(m)
128 if err != nil {
129 return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REP")
130 }
131 mk = asn1tools.AddASNAppTag(mk, asnAppTag.ASREP)
132 return mk, nil
133}
134
135// Unmarshal bytes b into the TGSRep struct.
136func (k *TGSRep) Unmarshal(b []byte) error {
137 var m marshalKDCRep
138 _, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREP))
139 if err != nil {
140 return processUnmarshalReplyError(b, err)
141 }
142 if m.MsgType != msgtype.KRB_TGS_REP {
143 return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an TGS_REP. Expected: %v; Actual: %v", msgtype.KRB_TGS_REP, m.MsgType)
144 }
145 //Process the raw ticket within
146 tkt, err := unmarshalTicket(m.Ticket.Bytes)
147 if err != nil {
148 return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within TGS_REP")
149 }
150 k.KDCRepFields = KDCRepFields{
151 PVNO: m.PVNO,
152 MsgType: m.MsgType,
153 PAData: m.PAData,
154 CRealm: m.CRealm,
155 CName: m.CName,
156 Ticket: tkt,
157 EncPart: m.EncPart,
158 }
159 return nil
160}
161
162// Marshal TGSRep struct.
163func (k *TGSRep) Marshal() ([]byte, error) {
164 m := marshalKDCRep{
165 PVNO: k.PVNO,
166 MsgType: k.MsgType,
167 PAData: k.PAData,
168 CRealm: k.CRealm,
169 CName: k.CName,
170 EncPart: k.EncPart,
171 }
172 b, err := k.Ticket.Marshal()
173 if err != nil {
174 return []byte{}, err
175 }
176 m.Ticket = asn1.RawValue{
177 Class: asn1.ClassContextSpecific,
178 IsCompound: true,
179 Tag: 5,
180 Bytes: b,
181 }
182 mk, err := asn1.Marshal(m)
183 if err != nil {
184 return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling TGS_REP")
185 }
186 mk = asn1tools.AddASNAppTag(mk, asnAppTag.TGSREP)
187 return mk, nil
188}
189
190// Unmarshal bytes b into encrypted part of KRB_KDC_REP.
191func (e *EncKDCRepPart) Unmarshal(b []byte) error {
192 _, err := asn1.UnmarshalWithParams(b, e, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncASRepPart))
193 if err != nil {
194 // Try using tag 26
195 // Ref: RFC 4120 - mentions that some implementations use application tag number 26 wether or not the reply is
196 // a AS-REP or a TGS-REP.
197 _, err = asn1.UnmarshalWithParams(b, e, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncTGSRepPart))
198 if err != nil {
199 return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part within KDC_REP")
200 }
201 }
202 return nil
203}
204
205// Marshal encrypted part of KRB_KDC_REP.
206func (e *EncKDCRepPart) Marshal() ([]byte, error) {
207 b, err := asn1.Marshal(*e)
208 if err != nil {
209 return b, krberror.Errorf(err, krberror.EncodingError, "marshaling error of AS_REP encpart")
210 }
211 b = asn1tools.AddASNAppTag(b, asnAppTag.EncASRepPart)
212 return b, nil
213}
214
215// DecryptEncPart decrypts the encrypted part of an AS_REP.
216func (k *ASRep) DecryptEncPart(c *credentials.Credentials) (types.EncryptionKey, error) {
217 var key types.EncryptionKey
218 var err error
219 if c.HasKeytab() {
220 key, _, err = c.Keytab().GetEncryptionKey(k.CName, k.CRealm, k.EncPart.KVNO, k.EncPart.EType)
221 if err != nil {
222 return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part")
223 }
224 }
225 if c.HasPassword() {
226 key, _, err = crypto.GetKeyFromPassword(c.Password(), k.CName, k.CRealm, k.EncPart.EType, k.PAData)
227 if err != nil {
228 return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part")
229 }
230 }
231 if !c.HasKeytab() && !c.HasPassword() {
232 return key, krberror.NewErrorf(krberror.DecryptingError, "no secret available in credentials to perform decryption of AS_REP encrypted part")
233 }
234 b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.AS_REP_ENCPART)
235 if err != nil {
236 return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part")
237 }
238 var denc EncKDCRepPart
239 err = denc.Unmarshal(b)
240 if err != nil {
241 return key, krberror.Errorf(err, krberror.EncodingError, "error unmarshaling decrypted encpart of AS_REP")
242 }
243 k.DecryptedEncPart = denc
244 return key, nil
245}
246
247// Verify checks the validity of AS_REP message.
248func (k *ASRep) Verify(cfg *config.Config, creds *credentials.Credentials, asReq ASReq) (bool, error) {
249 //Ref RFC 4120 Section 3.1.5
250 if !k.CName.Equal(asReq.ReqBody.CName) {
251 return false, krberror.NewErrorf(krberror.KRBMsgError, "CName in response does not match what was requested. Requested: %+v; Reply: %+v", asReq.ReqBody.CName, k.CName)
252 }
253 if k.CRealm != asReq.ReqBody.Realm {
254 return false, krberror.NewErrorf(krberror.KRBMsgError, "CRealm in response does not match what was requested. Requested: %s; Reply: %s", asReq.ReqBody.Realm, k.CRealm)
255 }
256 key, err := k.DecryptEncPart(creds)
257 if err != nil {
258 return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting EncPart of AS_REP")
259 }
260 if k.DecryptedEncPart.Nonce != asReq.ReqBody.Nonce {
261 return false, krberror.NewErrorf(krberror.KRBMsgError, "possible replay attack, nonce in response does not match that in request")
262 }
263 if !k.DecryptedEncPart.SName.Equal(asReq.ReqBody.SName) {
264 return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %v; Reply: %v", asReq.ReqBody.SName, k.DecryptedEncPart.SName)
265 }
266 if k.DecryptedEncPart.SRealm != asReq.ReqBody.Realm {
267 return false, krberror.NewErrorf(krberror.KRBMsgError, "SRealm in response does not match what was requested. Requested: %s; Reply: %s", asReq.ReqBody.Realm, k.DecryptedEncPart.SRealm)
268 }
269 if len(asReq.ReqBody.Addresses) > 0 {
270 if !types.HostAddressesEqual(k.DecryptedEncPart.CAddr, asReq.ReqBody.Addresses) {
271 return false, krberror.NewErrorf(krberror.KRBMsgError, "addresses listed in the AS_REP does not match those listed in the AS_REQ")
272 }
273 }
274 t := time.Now().UTC()
275 if t.Sub(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.AuthTime.Sub(t) > cfg.LibDefaults.Clockskew {
276 return false, krberror.NewErrorf(krberror.KRBMsgError, "clock skew with KDC too large. Greater than %v seconds", cfg.LibDefaults.Clockskew.Seconds())
277 }
278 // RFC 6806 https://tools.ietf.org/html/rfc6806.html#section-11
279 if asReq.PAData.Contains(patype.PA_REQ_ENC_PA_REP) && types.IsFlagSet(&k.DecryptedEncPart.Flags, flags.EncPARep) {
280 if len(k.DecryptedEncPart.EncPAData) < 2 || !k.DecryptedEncPart.EncPAData.Contains(patype.PA_FX_FAST) {
281 return false, krberror.NewErrorf(krberror.KRBMsgError, "KDC did not respond appropriately to FAST negotiation")
282 }
283 for _, pa := range k.DecryptedEncPart.EncPAData {
284 if pa.PADataType == patype.PA_REQ_ENC_PA_REP {
285 var pafast types.PAReqEncPARep
286 err := pafast.Unmarshal(pa.PADataValue)
287 if err != nil {
288 return false, krberror.Errorf(err, krberror.EncodingError, "KDC FAST negotiation response error, could not unmarshal PA_REQ_ENC_PA_REP")
289 }
290 etype, err := crypto.GetChksumEtype(pafast.ChksumType)
291 if err != nil {
292 return false, krberror.Errorf(err, krberror.ChksumError, "KDC FAST negotiation response error")
293 }
294 ab, _ := asReq.Marshal()
295 if !etype.VerifyChecksum(key.KeyValue, ab, pafast.Chksum, keyusage.KEY_USAGE_AS_REQ) {
296 return false, krberror.Errorf(err, krberror.ChksumError, "KDC FAST negotiation response checksum invalid")
297 }
298 }
299 }
300 }
301 return true, nil
302}
303
304// DecryptEncPart decrypts the encrypted part of an TGS_REP.
305func (k *TGSRep) DecryptEncPart(key types.EncryptionKey) error {
306 b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.TGS_REP_ENCPART_SESSION_KEY)
307 if err != nil {
308 return krberror.Errorf(err, krberror.DecryptingError, "error decrypting TGS_REP EncPart")
309 }
310 var denc EncKDCRepPart
311 err = denc.Unmarshal(b)
312 if err != nil {
313 return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part")
314 }
315 k.DecryptedEncPart = denc
316 return nil
317}
318
319// Verify checks the validity of the TGS_REP message.
320func (k *TGSRep) Verify(cfg *config.Config, tgsReq TGSReq) (bool, error) {
321 if !k.CName.Equal(tgsReq.ReqBody.CName) {
322 return false, krberror.NewErrorf(krberror.KRBMsgError, "CName in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.CName, k.CName)
323 }
324 if k.Ticket.Realm != tgsReq.ReqBody.Realm {
325 return false, krberror.NewErrorf(krberror.KRBMsgError, "realm in response ticket does not match what was requested. Requested: %s; Reply: %s", tgsReq.ReqBody.Realm, k.Ticket.Realm)
326 }
327 if k.DecryptedEncPart.Nonce != tgsReq.ReqBody.Nonce {
328 return false, krberror.NewErrorf(krberror.KRBMsgError, "possible replay attack, nonce in response does not match that in request")
329 }
330 //if k.Ticket.SName.NameType != tgsReq.ReqBody.SName.NameType || k.Ticket.SName.NameString == nil {
331 // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response ticket does not match what was requested. Requested: %v; Reply: %v", tgsReq.ReqBody.SName, k.Ticket.SName)
332 //}
333 //for i := range k.Ticket.SName.NameString {
334 // if k.Ticket.SName.NameString[i] != tgsReq.ReqBody.SName.NameString[i] {
335 // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response ticket does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.SName, k.Ticket.SName)
336 // }
337 //}
338 //if k.DecryptedEncPart.SName.NameType != tgsReq.ReqBody.SName.NameType || k.DecryptedEncPart.SName.NameString == nil {
339 // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %v; Reply: %v", tgsReq.ReqBody.SName, k.DecryptedEncPart.SName)
340 //}
341 //for i := range k.DecryptedEncPart.SName.NameString {
342 // if k.DecryptedEncPart.SName.NameString[i] != tgsReq.ReqBody.SName.NameString[i] {
343 // return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.SName, k.DecryptedEncPart.SName)
344 // }
345 //}
346 if k.DecryptedEncPart.SRealm != tgsReq.ReqBody.Realm {
347 return false, krberror.NewErrorf(krberror.KRBMsgError, "SRealm in response does not match what was requested. Requested: %s; Reply: %s", tgsReq.ReqBody.Realm, k.DecryptedEncPart.SRealm)
348 }
349 if len(k.DecryptedEncPart.CAddr) > 0 {
350 if !types.HostAddressesEqual(k.DecryptedEncPart.CAddr, tgsReq.ReqBody.Addresses) {
351 return false, krberror.NewErrorf(krberror.KRBMsgError, "addresses listed in the TGS_REP does not match those listed in the TGS_REQ")
352 }
353 }
354 if time.Since(k.DecryptedEncPart.StartTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.StartTime.Sub(time.Now().UTC()) > cfg.LibDefaults.Clockskew {
355 if time.Since(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.AuthTime.Sub(time.Now().UTC()) > cfg.LibDefaults.Clockskew {
356 return false, krberror.NewErrorf(krberror.KRBMsgError, "clock skew with KDC too large. Greater than %v seconds.", cfg.LibDefaults.Clockskew.Seconds())
357 }
358 }
359 return true, nil
360}