1package credentials 2 3import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "io/ioutil" 8 "strings" 9 "time" 10 "unsafe" 11 12 "github.com/jcmturner/gofork/encoding/asn1" 13 "github.com/jcmturner/gokrb5/v8/types" 14) 15 16const ( 17 headerFieldTagKDCOffset = 1 18) 19 20// The first byte of the file always has the value 5. 21// The value of the second byte contains the version number (1 through 4) 22// Versions 1 and 2 of the file format use native byte order for integer representations. 23// Versions 3 and 4 always use big-endian byte order 24// After the two-byte version indicator, the file has three parts: 25// 1) the header (in version 4 only) 26// 2) the default principal name 27// 3) a sequence of credentials 28 29// CCache is the file credentials cache as define here: https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html 30type CCache struct { 31 Version uint8 32 Header header 33 DefaultPrincipal principal 34 Credentials []*Credential 35 Path string 36} 37 38type header struct { 39 length uint16 40 fields []headerField 41} 42 43type headerField struct { 44 tag uint16 45 length uint16 46 value []byte 47} 48 49// Credential cache entry principal struct. 50type principal struct { 51 Realm string 52 PrincipalName types.PrincipalName 53} 54 55// Credential holds a Kerberos client's ccache credential information. 56type Credential struct { 57 Client principal 58 Server principal 59 Key types.EncryptionKey 60 AuthTime time.Time 61 StartTime time.Time 62 EndTime time.Time 63 RenewTill time.Time 64 IsSKey bool 65 TicketFlags asn1.BitString 66 Addresses []types.HostAddress 67 AuthData []types.AuthorizationDataEntry 68 Ticket []byte 69 SecondTicket []byte 70} 71 72// LoadCCache loads a credential cache file into a CCache type. 73func LoadCCache(cpath string) (*CCache, error) { 74 c := new(CCache) 75 b, err := ioutil.ReadFile(cpath) 76 if err != nil { 77 return c, err 78 } 79 err = c.Unmarshal(b) 80 return c, err 81} 82 83// Unmarshal a byte slice of credential cache data into CCache type. 84func (c *CCache) Unmarshal(b []byte) error { 85 p := 0 86 //The first byte of the file always has the value 5 87 if int8(b[p]) != 5 { 88 return errors.New("Invalid credential cache data. First byte does not equal 5") 89 } 90 p++ 91 //Get credential cache version 92 //The second byte contains the version number (1 to 4) 93 c.Version = b[p] 94 if c.Version < 1 || c.Version > 4 { 95 return errors.New("Invalid credential cache data. Keytab version is not within 1 to 4") 96 } 97 p++ 98 //Version 1 or 2 of the file format uses native byte order for integer representations. Versions 3 & 4 always uses big-endian byte order 99 var endian binary.ByteOrder 100 endian = binary.BigEndian 101 if (c.Version == 1 || c.Version == 2) && isNativeEndianLittle() { 102 endian = binary.LittleEndian 103 } 104 if c.Version == 4 { 105 err := parseHeader(b, &p, c, &endian) 106 if err != nil { 107 return err 108 } 109 } 110 c.DefaultPrincipal = parsePrincipal(b, &p, c, &endian) 111 for p < len(b) { 112 cred, err := parseCredential(b, &p, c, &endian) 113 if err != nil { 114 return err 115 } 116 c.Credentials = append(c.Credentials, cred) 117 } 118 return nil 119} 120 121func parseHeader(b []byte, p *int, c *CCache, e *binary.ByteOrder) error { 122 if c.Version != 4 { 123 return errors.New("Credentials cache version is not 4 so there is no header to parse.") 124 } 125 h := header{} 126 h.length = uint16(readInt16(b, p, e)) 127 for *p <= int(h.length) { 128 f := headerField{} 129 f.tag = uint16(readInt16(b, p, e)) 130 f.length = uint16(readInt16(b, p, e)) 131 f.value = b[*p : *p+int(f.length)] 132 *p += int(f.length) 133 if !f.valid() { 134 return errors.New("Invalid credential cache header found") 135 } 136 h.fields = append(h.fields, f) 137 } 138 c.Header = h 139 return nil 140} 141 142// Parse the Keytab bytes of a principal into a Keytab entry's principal. 143func parsePrincipal(b []byte, p *int, c *CCache, e *binary.ByteOrder) (princ principal) { 144 if c.Version != 1 { 145 //Name Type is omitted in version 1 146 princ.PrincipalName.NameType = readInt32(b, p, e) 147 } 148 nc := int(readInt32(b, p, e)) 149 if c.Version == 1 { 150 //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2 151 nc-- 152 } 153 lenRealm := readInt32(b, p, e) 154 princ.Realm = string(readBytes(b, p, int(lenRealm), e)) 155 for i := 0; i < nc; i++ { 156 l := readInt32(b, p, e) 157 princ.PrincipalName.NameString = append(princ.PrincipalName.NameString, string(readBytes(b, p, int(l), e))) 158 } 159 return princ 160} 161 162func parseCredential(b []byte, p *int, c *CCache, e *binary.ByteOrder) (cred *Credential, err error) { 163 cred = new(Credential) 164 cred.Client = parsePrincipal(b, p, c, e) 165 cred.Server = parsePrincipal(b, p, c, e) 166 key := types.EncryptionKey{} 167 key.KeyType = int32(readInt16(b, p, e)) 168 if c.Version == 3 { 169 //repeated twice in version 3 170 key.KeyType = int32(readInt16(b, p, e)) 171 } 172 key.KeyValue = readData(b, p, e) 173 cred.Key = key 174 cred.AuthTime = readTimestamp(b, p, e) 175 cred.StartTime = readTimestamp(b, p, e) 176 cred.EndTime = readTimestamp(b, p, e) 177 cred.RenewTill = readTimestamp(b, p, e) 178 if ik := readInt8(b, p, e); ik == 0 { 179 cred.IsSKey = false 180 } else { 181 cred.IsSKey = true 182 } 183 cred.TicketFlags = types.NewKrbFlags() 184 cred.TicketFlags.Bytes = readBytes(b, p, 4, e) 185 l := int(readInt32(b, p, e)) 186 cred.Addresses = make([]types.HostAddress, l, l) 187 for i := range cred.Addresses { 188 cred.Addresses[i] = readAddress(b, p, e) 189 } 190 l = int(readInt32(b, p, e)) 191 cred.AuthData = make([]types.AuthorizationDataEntry, l, l) 192 for i := range cred.AuthData { 193 cred.AuthData[i] = readAuthDataEntry(b, p, e) 194 } 195 cred.Ticket = readData(b, p, e) 196 cred.SecondTicket = readData(b, p, e) 197 return 198} 199 200// GetClientPrincipalName returns a PrincipalName type for the client the credentials cache is for. 201func (c *CCache) GetClientPrincipalName() types.PrincipalName { 202 return c.DefaultPrincipal.PrincipalName 203} 204 205// GetClientRealm returns the reals of the client the credentials cache is for. 206func (c *CCache) GetClientRealm() string { 207 return c.DefaultPrincipal.Realm 208} 209 210// GetClientCredentials returns a Credentials object representing the client of the credentials cache. 211func (c *CCache) GetClientCredentials() *Credentials { 212 return &Credentials{ 213 username: c.DefaultPrincipal.PrincipalName.PrincipalNameString(), 214 realm: c.GetClientRealm(), 215 cname: c.DefaultPrincipal.PrincipalName, 216 } 217} 218 219// Contains tests if the cache contains a credential for the provided server PrincipalName 220func (c *CCache) Contains(p types.PrincipalName) bool { 221 for _, cred := range c.Credentials { 222 if cred.Server.PrincipalName.Equal(p) { 223 return true 224 } 225 } 226 return false 227} 228 229// GetEntry returns a specific credential for the PrincipalName provided. 230func (c *CCache) GetEntry(p types.PrincipalName) (*Credential, bool) { 231 cred := new(Credential) 232 var found bool 233 for i := range c.Credentials { 234 if c.Credentials[i].Server.PrincipalName.Equal(p) { 235 cred = c.Credentials[i] 236 found = true 237 break 238 } 239 } 240 if !found { 241 return cred, false 242 } 243 return cred, true 244} 245 246// GetEntries filters out configuration entries an returns a slice of credentials. 247func (c *CCache) GetEntries() []*Credential { 248 creds := make([]*Credential, 0) 249 for _, cred := range c.Credentials { 250 // Filter out configuration entries 251 if strings.HasPrefix(cred.Server.Realm, "X-CACHECONF") { 252 continue 253 } 254 creds = append(creds, cred) 255 } 256 return creds 257} 258 259func (h *headerField) valid() bool { 260 // At this time there is only one defined header field. 261 // Its tag value is 1, its length is always 8. 262 // Its contents are two 32-bit integers giving the seconds and microseconds 263 // of the time offset of the KDC relative to the client. 264 // Adding this offset to the current time on the client should give the current time on the KDC, if that offset has not changed since the initial authentication. 265 266 // Done as a switch in case other tag values are added in the future. 267 switch h.tag { 268 case headerFieldTagKDCOffset: 269 if h.length != 8 || len(h.value) != 8 { 270 return false 271 } 272 return true 273 } 274 return false 275} 276 277func readData(b []byte, p *int, e *binary.ByteOrder) []byte { 278 l := readInt32(b, p, e) 279 return readBytes(b, p, int(l), e) 280} 281 282func readAddress(b []byte, p *int, e *binary.ByteOrder) types.HostAddress { 283 a := types.HostAddress{} 284 a.AddrType = int32(readInt16(b, p, e)) 285 a.Address = readData(b, p, e) 286 return a 287} 288 289func readAuthDataEntry(b []byte, p *int, e *binary.ByteOrder) types.AuthorizationDataEntry { 290 a := types.AuthorizationDataEntry{} 291 a.ADType = int32(readInt16(b, p, e)) 292 a.ADData = readData(b, p, e) 293 return a 294} 295 296// Read bytes representing a timestamp. 297func readTimestamp(b []byte, p *int, e *binary.ByteOrder) time.Time { 298 return time.Unix(int64(readInt32(b, p, e)), 0) 299} 300 301// Read bytes representing an eight bit integer. 302func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8) { 303 buf := bytes.NewBuffer(b[*p : *p+1]) 304 binary.Read(buf, *e, &i) 305 *p++ 306 return 307} 308 309// Read bytes representing a sixteen bit integer. 310func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16) { 311 buf := bytes.NewBuffer(b[*p : *p+2]) 312 binary.Read(buf, *e, &i) 313 *p += 2 314 return 315} 316 317// Read bytes representing a thirty two bit integer. 318func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32) { 319 buf := bytes.NewBuffer(b[*p : *p+4]) 320 binary.Read(buf, *e, &i) 321 *p += 4 322 return 323} 324 325func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) []byte { 326 buf := bytes.NewBuffer(b[*p : *p+s]) 327 r := make([]byte, s) 328 binary.Read(buf, *e, &r) 329 *p += s 330 return r 331} 332 333func isNativeEndianLittle() bool { 334 var x = 0x012345678 335 var p = unsafe.Pointer(&x) 336 var bp = (*[4]byte)(p) 337 338 var endian bool 339 if 0x01 == bp[0] { 340 endian = false 341 } else if (0x78 & 0xff) == (bp[0] & 0xff) { 342 endian = true 343 } else { 344 // Default to big endian 345 endian = false 346 } 347 return endian 348} 349