1package messages
2
3// Reference: https://www.ietf.org/rfc/rfc4120.txt
4// Section: 5.4.1
5
6import (
7	"crypto/rand"
8	"fmt"
9	"math"
10	"math/big"
11	"time"
12
13	"github.com/jcmturner/gofork/encoding/asn1"
14	"github.com/jcmturner/gokrb5/v8/asn1tools"
15	"github.com/jcmturner/gokrb5/v8/config"
16	"github.com/jcmturner/gokrb5/v8/crypto"
17	"github.com/jcmturner/gokrb5/v8/iana"
18	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
19	"github.com/jcmturner/gokrb5/v8/iana/flags"
20	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
21	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
22	"github.com/jcmturner/gokrb5/v8/iana/nametype"
23	"github.com/jcmturner/gokrb5/v8/iana/patype"
24	"github.com/jcmturner/gokrb5/v8/krberror"
25	"github.com/jcmturner/gokrb5/v8/types"
26)
27
28type marshalKDCReq struct {
29	PVNO    int                  `asn1:"explicit,tag:1"`
30	MsgType int                  `asn1:"explicit,tag:2"`
31	PAData  types.PADataSequence `asn1:"explicit,optional,tag:3"`
32	ReqBody asn1.RawValue        `asn1:"explicit,tag:4"`
33}
34
35// KDCReqFields represents the KRB_KDC_REQ fields.
36type KDCReqFields struct {
37	PVNO    int
38	MsgType int
39	PAData  types.PADataSequence
40	ReqBody KDCReqBody
41	Renewal bool
42}
43
44// ASReq implements RFC 4120 KRB_AS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1.
45type ASReq struct {
46	KDCReqFields
47}
48
49// TGSReq implements RFC 4120 KRB_TGS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1.
50type TGSReq struct {
51	KDCReqFields
52}
53
54type marshalKDCReqBody struct {
55	KDCOptions  asn1.BitString      `asn1:"explicit,tag:0"`
56	CName       types.PrincipalName `asn1:"explicit,optional,tag:1"`
57	Realm       string              `asn1:"generalstring,explicit,tag:2"`
58	SName       types.PrincipalName `asn1:"explicit,optional,tag:3"`
59	From        time.Time           `asn1:"generalized,explicit,optional,tag:4"`
60	Till        time.Time           `asn1:"generalized,explicit,tag:5"`
61	RTime       time.Time           `asn1:"generalized,explicit,optional,tag:6"`
62	Nonce       int                 `asn1:"explicit,tag:7"`
63	EType       []int32             `asn1:"explicit,tag:8"`
64	Addresses   []types.HostAddress `asn1:"explicit,optional,tag:9"`
65	EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"`
66	// Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
67	AdditionalTickets asn1.RawValue `asn1:"explicit,optional,tag:11"`
68}
69
70// KDCReqBody implements the KRB_KDC_REQ request body.
71type KDCReqBody struct {
72	KDCOptions        asn1.BitString      `asn1:"explicit,tag:0"`
73	CName             types.PrincipalName `asn1:"explicit,optional,tag:1"`
74	Realm             string              `asn1:"generalstring,explicit,tag:2"`
75	SName             types.PrincipalName `asn1:"explicit,optional,tag:3"`
76	From              time.Time           `asn1:"generalized,explicit,optional,tag:4"`
77	Till              time.Time           `asn1:"generalized,explicit,tag:5"`
78	RTime             time.Time           `asn1:"generalized,explicit,optional,tag:6"`
79	Nonce             int                 `asn1:"explicit,tag:7"`
80	EType             []int32             `asn1:"explicit,tag:8"`
81	Addresses         []types.HostAddress `asn1:"explicit,optional,tag:9"`
82	EncAuthData       types.EncryptedData `asn1:"explicit,optional,tag:10"`
83	AdditionalTickets []Ticket            `asn1:"explicit,optional,tag:11"`
84}
85
86// NewASReqForTGT generates a new KRB_AS_REQ struct for a TGT request.
87func NewASReqForTGT(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) {
88	sname := types.PrincipalName{
89		NameType:   nametype.KRB_NT_SRV_INST,
90		NameString: []string{"krbtgt", realm},
91	}
92	return NewASReq(realm, c, cname, sname)
93}
94
95// NewASReqForChgPasswd generates a new KRB_AS_REQ struct for a change password request.
96func NewASReqForChgPasswd(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) {
97	sname := types.PrincipalName{
98		NameType:   nametype.KRB_NT_PRINCIPAL,
99		NameString: []string{"kadmin", "changepw"},
100	}
101	return NewASReq(realm, c, cname, sname)
102}
103
104// NewASReq generates a new KRB_AS_REQ struct for a given SNAME.
105func NewASReq(realm string, c *config.Config, cname, sname types.PrincipalName) (ASReq, error) {
106	nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
107	if err != nil {
108		return ASReq{}, err
109	}
110	t := time.Now().UTC()
111	// Copy the default options to make this thread safe
112	kopts := types.NewKrbFlags()
113	copy(kopts.Bytes, c.LibDefaults.KDCDefaultOptions.Bytes)
114	kopts.BitLength = c.LibDefaults.KDCDefaultOptions.BitLength
115	a := ASReq{
116		KDCReqFields{
117			PVNO:    iana.PVNO,
118			MsgType: msgtype.KRB_AS_REQ,
119			PAData:  types.PADataSequence{},
120			ReqBody: KDCReqBody{
121				KDCOptions: kopts,
122				Realm:      realm,
123				CName:      cname,
124				SName:      sname,
125				Till:       t.Add(c.LibDefaults.TicketLifetime),
126				Nonce:      int(nonce.Int64()),
127				EType:      c.LibDefaults.DefaultTktEnctypeIDs,
128			},
129		},
130	}
131	if c.LibDefaults.Forwardable {
132		types.SetFlag(&a.ReqBody.KDCOptions, flags.Forwardable)
133	}
134	if c.LibDefaults.Canonicalize {
135		types.SetFlag(&a.ReqBody.KDCOptions, flags.Canonicalize)
136	}
137	if c.LibDefaults.Proxiable {
138		types.SetFlag(&a.ReqBody.KDCOptions, flags.Proxiable)
139	}
140	if c.LibDefaults.RenewLifetime != 0 {
141		types.SetFlag(&a.ReqBody.KDCOptions, flags.Renewable)
142		a.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime)
143		a.ReqBody.RTime = t.Add(time.Duration(48) * time.Hour)
144	}
145	if !c.LibDefaults.NoAddresses {
146		ha, err := types.LocalHostAddresses()
147		if err != nil {
148			return a, fmt.Errorf("could not get local addresses: %v", err)
149		}
150		ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...)
151		a.ReqBody.Addresses = ha
152	}
153	return a, nil
154}
155
156// NewTGSReq generates a new KRB_TGS_REQ struct.
157func NewTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, tgt Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool) (TGSReq, error) {
158	a, err := tgsReq(cname, sname, kdcRealm, renewal, c)
159	if err != nil {
160		return a, err
161	}
162	err = a.setPAData(tgt, sessionKey)
163	return a, err
164}
165
166// NewUser2UserTGSReq returns a TGS-REQ suitable for user-to-user authentication (https://tools.ietf.org/html/rfc4120#section-3.7)
167func NewUser2UserTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, clientTGT Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool, verifyingTGT Ticket) (TGSReq, error) {
168	a, err := tgsReq(cname, sname, kdcRealm, renewal, c)
169	if err != nil {
170		return a, err
171	}
172	a.ReqBody.AdditionalTickets = []Ticket{verifyingTGT}
173	types.SetFlag(&a.ReqBody.KDCOptions, flags.EncTktInSkey)
174	err = a.setPAData(clientTGT, sessionKey)
175	return a, err
176}
177
178// tgsReq populates the fields for a TGS_REQ
179func tgsReq(cname, sname types.PrincipalName, kdcRealm string, renewal bool, c *config.Config) (TGSReq, error) {
180	nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
181	if err != nil {
182		return TGSReq{}, err
183	}
184	t := time.Now().UTC()
185	k := KDCReqFields{
186		PVNO:    iana.PVNO,
187		MsgType: msgtype.KRB_TGS_REQ,
188		ReqBody: KDCReqBody{
189			KDCOptions: types.NewKrbFlags(),
190			Realm:      kdcRealm,
191			CName:      cname, // Add the CName to make validation of the reply easier
192			SName:      sname,
193			Till:       t.Add(c.LibDefaults.TicketLifetime),
194			Nonce:      int(nonce.Int64()),
195			EType:      c.LibDefaults.DefaultTGSEnctypeIDs,
196		},
197		Renewal: renewal,
198	}
199	if c.LibDefaults.Forwardable {
200		types.SetFlag(&k.ReqBody.KDCOptions, flags.Forwardable)
201	}
202	if c.LibDefaults.Canonicalize {
203		types.SetFlag(&k.ReqBody.KDCOptions, flags.Canonicalize)
204	}
205	if c.LibDefaults.Proxiable {
206		types.SetFlag(&k.ReqBody.KDCOptions, flags.Proxiable)
207	}
208	if c.LibDefaults.RenewLifetime > time.Duration(0) {
209		types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable)
210		k.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime)
211	}
212	if !c.LibDefaults.NoAddresses {
213		ha, err := types.LocalHostAddresses()
214		if err != nil {
215			return TGSReq{}, fmt.Errorf("could not get local addresses: %v", err)
216		}
217		ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...)
218		k.ReqBody.Addresses = ha
219	}
220	if renewal {
221		types.SetFlag(&k.ReqBody.KDCOptions, flags.Renew)
222		types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable)
223	}
224	return TGSReq{
225		k,
226	}, nil
227}
228
229func (k *TGSReq) setPAData(tgt Ticket, sessionKey types.EncryptionKey) error {
230	// Marshal the request and calculate checksum
231	b, err := k.ReqBody.Marshal()
232	if err != nil {
233		return krberror.Errorf(err, krberror.EncodingError, "error marshaling TGS_REQ body")
234	}
235	etype, err := crypto.GetEtype(sessionKey.KeyType)
236	if err != nil {
237		return krberror.Errorf(err, krberror.EncryptingError, "error getting etype to encrypt authenticator")
238	}
239	cb, err := etype.GetChecksumHash(sessionKey.KeyValue, b, keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM)
240	if err != nil {
241		return krberror.Errorf(err, krberror.ChksumError, "error getting etype checksum hash")
242	}
243
244	// Form PAData for TGS_REQ
245	// Create authenticator
246	auth, err := types.NewAuthenticator(tgt.Realm, k.ReqBody.CName)
247	if err != nil {
248		return krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
249	}
250	auth.Cksum = types.Checksum{
251		CksumType: etype.GetHashID(),
252		Checksum:  cb,
253	}
254	// Create AP_REQ
255	apReq, err := NewAPReq(tgt, sessionKey, auth)
256	if err != nil {
257		return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AP_REQ")
258	}
259	apb, err := apReq.Marshal()
260	if err != nil {
261		return krberror.Errorf(err, krberror.EncodingError, "error marshaling AP_REQ for pre-authentication data")
262	}
263	k.PAData = types.PADataSequence{
264		types.PAData{
265			PADataType:  patype.PA_TGS_REQ,
266			PADataValue: apb,
267		},
268	}
269	return nil
270}
271
272// Unmarshal bytes b into the ASReq struct.
273func (k *ASReq) Unmarshal(b []byte) error {
274	var m marshalKDCReq
275	_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREQ))
276	if err != nil {
277		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling AS_REQ")
278	}
279	expectedMsgType := msgtype.KRB_AS_REQ
280	if m.MsgType != expectedMsgType {
281		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a AS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType)
282	}
283	var reqb KDCReqBody
284	err = reqb.Unmarshal(m.ReqBody.Bytes)
285	if err != nil {
286		return krberror.Errorf(err, krberror.EncodingError, "error processing AS_REQ body")
287	}
288	k.MsgType = m.MsgType
289	k.PAData = m.PAData
290	k.PVNO = m.PVNO
291	k.ReqBody = reqb
292	return nil
293}
294
295// Unmarshal bytes b into the TGSReq struct.
296func (k *TGSReq) Unmarshal(b []byte) error {
297	var m marshalKDCReq
298	_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREQ))
299	if err != nil {
300		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling TGS_REQ")
301	}
302	expectedMsgType := msgtype.KRB_TGS_REQ
303	if m.MsgType != expectedMsgType {
304		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a TGS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType)
305	}
306	var reqb KDCReqBody
307	err = reqb.Unmarshal(m.ReqBody.Bytes)
308	if err != nil {
309		return krberror.Errorf(err, krberror.EncodingError, "error processing TGS_REQ body")
310	}
311	k.MsgType = m.MsgType
312	k.PAData = m.PAData
313	k.PVNO = m.PVNO
314	k.ReqBody = reqb
315	return nil
316}
317
318// Unmarshal bytes b into the KRB_KDC_REQ body struct.
319func (k *KDCReqBody) Unmarshal(b []byte) error {
320	var m marshalKDCReqBody
321	_, err := asn1.Unmarshal(b, &m)
322	if err != nil {
323		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling KDC_REQ body")
324	}
325	k.KDCOptions = m.KDCOptions
326	if len(k.KDCOptions.Bytes) < 4 {
327		tb := make([]byte, 4-len(k.KDCOptions.Bytes))
328		k.KDCOptions.Bytes = append(tb, k.KDCOptions.Bytes...)
329		k.KDCOptions.BitLength = len(k.KDCOptions.Bytes) * 8
330	}
331	k.CName = m.CName
332	k.Realm = m.Realm
333	k.SName = m.SName
334	k.From = m.From
335	k.Till = m.Till
336	k.RTime = m.RTime
337	k.Nonce = m.Nonce
338	k.EType = m.EType
339	k.Addresses = m.Addresses
340	k.EncAuthData = m.EncAuthData
341	if len(m.AdditionalTickets.Bytes) > 0 {
342		k.AdditionalTickets, err = unmarshalTicketsSequence(m.AdditionalTickets)
343		if err != nil {
344			return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling additional tickets")
345		}
346	}
347	return nil
348}
349
350// Marshal ASReq struct.
351func (k *ASReq) Marshal() ([]byte, error) {
352	m := marshalKDCReq{
353		PVNO:    k.PVNO,
354		MsgType: k.MsgType,
355		PAData:  k.PAData,
356	}
357	b, err := k.ReqBody.Marshal()
358	if err != nil {
359		var mk []byte
360		return mk, err
361	}
362	m.ReqBody = asn1.RawValue{
363		Class:      asn1.ClassContextSpecific,
364		IsCompound: true,
365		Tag:        4,
366		Bytes:      b,
367	}
368	mk, err := asn1.Marshal(m)
369	if err != nil {
370		return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ")
371	}
372	mk = asn1tools.AddASNAppTag(mk, asnAppTag.ASREQ)
373	return mk, nil
374}
375
376// Marshal TGSReq struct.
377func (k *TGSReq) Marshal() ([]byte, error) {
378	m := marshalKDCReq{
379		PVNO:    k.PVNO,
380		MsgType: k.MsgType,
381		PAData:  k.PAData,
382	}
383	b, err := k.ReqBody.Marshal()
384	if err != nil {
385		var mk []byte
386		return mk, err
387	}
388	m.ReqBody = asn1.RawValue{
389		Class:      asn1.ClassContextSpecific,
390		IsCompound: true,
391		Tag:        4,
392		Bytes:      b,
393	}
394	mk, err := asn1.Marshal(m)
395	if err != nil {
396		return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ")
397	}
398	mk = asn1tools.AddASNAppTag(mk, asnAppTag.TGSREQ)
399	return mk, nil
400}
401
402// Marshal KRB_KDC_REQ body struct.
403func (k *KDCReqBody) Marshal() ([]byte, error) {
404	var b []byte
405	m := marshalKDCReqBody{
406		KDCOptions:  k.KDCOptions,
407		CName:       k.CName,
408		Realm:       k.Realm,
409		SName:       k.SName,
410		From:        k.From,
411		Till:        k.Till,
412		RTime:       k.RTime,
413		Nonce:       k.Nonce,
414		EType:       k.EType,
415		Addresses:   k.Addresses,
416		EncAuthData: k.EncAuthData,
417	}
418	rawtkts, err := MarshalTicketSequence(k.AdditionalTickets)
419	if err != nil {
420		return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body additional tickets")
421	}
422	//The asn1.rawValue needs the tag setting on it for where it is in the KDCReqBody
423	rawtkts.Tag = 11
424	if len(rawtkts.Bytes) > 0 {
425		m.AdditionalTickets = rawtkts
426	}
427	b, err = asn1.Marshal(m)
428	if err != nil {
429		return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body")
430	}
431	return b, nil
432}
433