blob: 11efad62c2c6ec9e0419fd6cb6ad829fc5f55c69 [file] [log] [blame]
David K. Bainbridgebd6b2882021-08-26 13:31:02 +00001package messages
2
3import (
4 "fmt"
5 "log"
6 "time"
7
8 "github.com/jcmturner/gofork/encoding/asn1"
9 "github.com/jcmturner/gokrb5/v8/asn1tools"
10 "github.com/jcmturner/gokrb5/v8/crypto"
11 "github.com/jcmturner/gokrb5/v8/iana"
12 "github.com/jcmturner/gokrb5/v8/iana/adtype"
13 "github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
14 "github.com/jcmturner/gokrb5/v8/iana/errorcode"
15 "github.com/jcmturner/gokrb5/v8/iana/flags"
16 "github.com/jcmturner/gokrb5/v8/iana/keyusage"
17 "github.com/jcmturner/gokrb5/v8/keytab"
18 "github.com/jcmturner/gokrb5/v8/krberror"
19 "github.com/jcmturner/gokrb5/v8/pac"
20 "github.com/jcmturner/gokrb5/v8/types"
21)
22
23// Reference: https://www.ietf.org/rfc/rfc4120.txt
24// Section: 5.3
25
26// Ticket implements the Kerberos ticket.
27type Ticket struct {
28 TktVNO int `asn1:"explicit,tag:0"`
29 Realm string `asn1:"generalstring,explicit,tag:1"`
30 SName types.PrincipalName `asn1:"explicit,tag:2"`
31 EncPart types.EncryptedData `asn1:"explicit,tag:3"`
32 DecryptedEncPart EncTicketPart `asn1:"optional"` // Not part of ASN1 bytes so marked as optional so unmarshalling works
33}
34
35// EncTicketPart is the encrypted part of the Ticket.
36type EncTicketPart struct {
37 Flags asn1.BitString `asn1:"explicit,tag:0"`
38 Key types.EncryptionKey `asn1:"explicit,tag:1"`
39 CRealm string `asn1:"generalstring,explicit,tag:2"`
40 CName types.PrincipalName `asn1:"explicit,tag:3"`
41 Transited TransitedEncoding `asn1:"explicit,tag:4"`
42 AuthTime time.Time `asn1:"generalized,explicit,tag:5"`
43 StartTime time.Time `asn1:"generalized,explicit,optional,tag:6"`
44 EndTime time.Time `asn1:"generalized,explicit,tag:7"`
45 RenewTill time.Time `asn1:"generalized,explicit,optional,tag:8"`
46 CAddr types.HostAddresses `asn1:"explicit,optional,tag:9"`
47 AuthorizationData types.AuthorizationData `asn1:"explicit,optional,tag:10"`
48}
49
50// TransitedEncoding part of the ticket's encrypted part.
51type TransitedEncoding struct {
52 TRType int32 `asn1:"explicit,tag:0"`
53 Contents []byte `asn1:"explicit,tag:1"`
54}
55
56// NewTicket creates a new Ticket instance.
57func NewTicket(cname types.PrincipalName, crealm string, sname types.PrincipalName, srealm string, flags asn1.BitString, sktab *keytab.Keytab, eTypeID int32, kvno int, authTime, startTime, endTime, renewTill time.Time) (Ticket, types.EncryptionKey, error) {
58 etype, err := crypto.GetEtype(eTypeID)
59 if err != nil {
60 return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting etype for new ticket")
61 }
62 sessionKey, err := types.GenerateEncryptionKey(etype)
63 if err != nil {
64 return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error generating session key")
65 }
66
67 etp := EncTicketPart{
68 Flags: flags,
69 Key: sessionKey,
70 CRealm: crealm,
71 CName: cname,
72 Transited: TransitedEncoding{},
73 AuthTime: authTime,
74 StartTime: startTime,
75 EndTime: endTime,
76 RenewTill: renewTill,
77 }
78 b, err := asn1.Marshal(etp)
79 if err != nil {
80 return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncodingError, "error marshalling ticket encpart")
81 }
82 b = asn1tools.AddASNAppTag(b, asnAppTag.EncTicketPart)
83 skey, _, err := sktab.GetEncryptionKey(sname, srealm, kvno, eTypeID)
84 if err != nil {
85 return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting encryption key for new ticket")
86 }
87 ed, err := crypto.GetEncryptedData(b, skey, keyusage.KDC_REP_TICKET, kvno)
88 if err != nil {
89 return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error encrypting ticket encpart")
90 }
91 tkt := Ticket{
92 TktVNO: iana.PVNO,
93 Realm: srealm,
94 SName: sname,
95 EncPart: ed,
96 }
97 return tkt, sessionKey, nil
98}
99
100// Unmarshal bytes b into a Ticket struct.
101func (t *Ticket) Unmarshal(b []byte) error {
102 _, err := asn1.UnmarshalWithParams(b, t, fmt.Sprintf("application,explicit,tag:%d", asnAppTag.Ticket))
103 return err
104}
105
106// Marshal the Ticket.
107func (t *Ticket) Marshal() ([]byte, error) {
108 b, err := asn1.Marshal(*t)
109 if err != nil {
110 return nil, err
111 }
112 b = asn1tools.AddASNAppTag(b, asnAppTag.Ticket)
113 return b, nil
114}
115
116// Unmarshal bytes b into the EncTicketPart struct.
117func (t *EncTicketPart) Unmarshal(b []byte) error {
118 _, err := asn1.UnmarshalWithParams(b, t, fmt.Sprintf("application,explicit,tag:%d", asnAppTag.EncTicketPart))
119 return err
120}
121
122// unmarshalTicket returns a ticket from the bytes provided.
123func unmarshalTicket(b []byte) (t Ticket, err error) {
124 err = t.Unmarshal(b)
125 return
126}
127
128// UnmarshalTicketsSequence returns a slice of Tickets from a raw ASN1 value.
129func unmarshalTicketsSequence(in asn1.RawValue) ([]Ticket, error) {
130 //This is a workaround to a asn1 decoding issue in golang - https://github.com/golang/go/issues/17321. It's not pretty I'm afraid
131 //We pull out raw values from the larger raw value (that is actually the data of the sequence of raw values) and track our position moving along the data.
132 b := in.Bytes
133 // Ignore the head of the asn1 stream (1 byte for tag and those for the length) as this is what tells us its a sequence but we're handling it ourselves
134 p := 1 + asn1tools.GetNumberBytesInLengthHeader(in.Bytes)
135 var tkts []Ticket
136 var raw asn1.RawValue
137 for p < (len(b)) {
138 _, err := asn1.UnmarshalWithParams(b[p:], &raw, fmt.Sprintf("application,tag:%d", asnAppTag.Ticket))
139 if err != nil {
140 return nil, fmt.Errorf("unmarshaling sequence of tickets failed getting length of ticket: %v", err)
141 }
142 t, err := unmarshalTicket(b[p:])
143 if err != nil {
144 return nil, fmt.Errorf("unmarshaling sequence of tickets failed: %v", err)
145 }
146 p += len(raw.FullBytes)
147 tkts = append(tkts, t)
148 }
149 MarshalTicketSequence(tkts)
150 return tkts, nil
151}
152
153// MarshalTicketSequence marshals a slice of Tickets returning an ASN1 raw value containing the ticket sequence.
154func MarshalTicketSequence(tkts []Ticket) (asn1.RawValue, error) {
155 raw := asn1.RawValue{
156 Class: 2,
157 IsCompound: true,
158 }
159 if len(tkts) < 1 {
160 // There are no tickets to marshal
161 return raw, nil
162 }
163 var btkts []byte
164 for i, t := range tkts {
165 b, err := t.Marshal()
166 if err != nil {
167 return raw, fmt.Errorf("error marshaling ticket number %d in sequence of tickets", i+1)
168 }
169 btkts = append(btkts, b...)
170 }
171 // The ASN1 wrapping consists of 2 bytes:
172 // 1st byte -> Identifier Octet - In this case an OCTET STRING (ASN TAG
173 // 2nd byte -> The length (this will be the size indicated in the input bytes + 2 for the additional bytes we add here.
174 // Application Tag:
175 //| Byte: | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
176 //| Value: | 0 | 1 | 1 | From the RFC spec 4120 |
177 //| Explanation | Defined by the ASN1 encoding rules for an application tag | A value of 1 indicates a constructed type | The ASN Application tag value |
178 btkts = append(asn1tools.MarshalLengthBytes(len(btkts)), btkts...)
179 btkts = append([]byte{byte(32 + asn1.TagSequence)}, btkts...)
180 raw.Bytes = btkts
181 // If we need to create the full bytes then identifier octet is "context-specific" = 128 + "constructed" + 32 + the wrapping explicit tag (11)
182 //fmt.Fprintf(os.Stderr, "mRaw fb: %v\n", raw.FullBytes)
183 return raw, nil
184}
185
186// DecryptEncPart decrypts the encrypted part of the ticket.
187// The sname argument can be used to specify which service principal's key should be used to decrypt the ticket.
188// If nil is passed as the sname then the service principal specified within the ticket it used.
189func (t *Ticket) DecryptEncPart(keytab *keytab.Keytab, sname *types.PrincipalName) error {
190 if sname == nil {
191 sname = &t.SName
192 }
193 key, _, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType)
194 if err != nil {
195 return NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err))
196 }
197 return t.Decrypt(key)
198}
199
200// Decrypt decrypts the encrypted part of the ticket using the key provided.
201func (t *Ticket) Decrypt(key types.EncryptionKey) error {
202 b, err := crypto.DecryptEncPart(t.EncPart, key, keyusage.KDC_REP_TICKET)
203 if err != nil {
204 return fmt.Errorf("error decrypting Ticket EncPart: %v", err)
205 }
206 var denc EncTicketPart
207 err = denc.Unmarshal(b)
208 if err != nil {
209 return fmt.Errorf("error unmarshaling encrypted part: %v", err)
210 }
211 t.DecryptedEncPart = denc
212 return nil
213}
214
215// GetPACType returns a Microsoft PAC that has been extracted from the ticket and processed.
216func (t *Ticket) GetPACType(keytab *keytab.Keytab, sname *types.PrincipalName, l *log.Logger) (bool, pac.PACType, error) {
217 var isPAC bool
218 for _, ad := range t.DecryptedEncPart.AuthorizationData {
219 if ad.ADType == adtype.ADIfRelevant {
220 var ad2 types.AuthorizationData
221 err := ad2.Unmarshal(ad.ADData)
222 if err != nil {
223 l.Printf("PAC authorization data could not be unmarshaled: %v", err)
224 continue
225 }
226 if ad2[0].ADType == adtype.ADWin2KPAC {
227 isPAC = true
228 var p pac.PACType
229 err = p.Unmarshal(ad2[0].ADData)
230 if err != nil {
231 return isPAC, p, fmt.Errorf("error unmarshaling PAC: %v", err)
232 }
233 if sname == nil {
234 sname = &t.SName
235 }
236 key, _, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType)
237 if err != nil {
238 return isPAC, p, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err))
239 }
240 err = p.ProcessPACInfoBuffers(key, l)
241 return isPAC, p, err
242 }
243 }
244 }
245 return isPAC, pac.PACType{}, nil
246}
247
248// Valid checks it the ticket is currently valid. Max duration passed endtime passed in as argument.
249func (t *Ticket) Valid(d time.Duration) (bool, error) {
250 // Check for future tickets or invalid tickets
251 time := time.Now().UTC()
252 if t.DecryptedEncPart.StartTime.Sub(time) > d || types.IsFlagSet(&t.DecryptedEncPart.Flags, flags.Invalid) {
253 return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_NYV, "service ticket provided is not yet valid")
254 }
255
256 // Check for expired ticket
257 if time.Sub(t.DecryptedEncPart.EndTime) > d {
258 return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_EXPIRED, "service ticket provided has expired")
259 }
260
261 return true, nil
262}