David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 1 | package messages |
| 2 | |
| 3 | import ( |
| 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. |
| 27 | type 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. |
| 36 | type 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. |
| 51 | type 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. |
| 57 | func 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. |
| 101 | func (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. |
| 107 | func (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. |
| 117 | func (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. |
| 123 | func 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. |
| 129 | func 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. |
| 154 | func 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. |
| 189 | func (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. |
| 201 | func (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. |
| 216 | func (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. |
| 249 | func (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 | } |