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