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