123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- package credentials
- import (
- "bytes"
- "encoding/binary"
- "errors"
- "os"
- "strings"
- "time"
- "unsafe"
- "github.com/jcmturner/gofork/encoding/asn1"
- "github.com/jcmturner/gokrb5/v8/types"
- )
- const (
- headerFieldTagKDCOffset = 1
- )
- // CCache is the file credentials cache as define here: https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html
- type CCache struct {
- Version uint8
- Header header
- DefaultPrincipal principal
- Credentials []*Credential
- Path string
- }
- type header struct {
- length uint16
- fields []headerField
- }
- type headerField struct {
- tag uint16
- length uint16
- value []byte
- }
- // Credential cache entry principal struct.
- type principal struct {
- Realm string
- PrincipalName types.PrincipalName
- }
- // Credential holds a Kerberos client's ccache credential information.
- type Credential struct {
- Client principal
- Server principal
- Key types.EncryptionKey
- AuthTime time.Time
- StartTime time.Time
- EndTime time.Time
- RenewTill time.Time
- IsSKey bool
- TicketFlags asn1.BitString
- Addresses []types.HostAddress
- AuthData []types.AuthorizationDataEntry
- Ticket []byte
- SecondTicket []byte
- }
- // LoadCCache loads a credential cache file into a CCache type.
- func LoadCCache(cpath string) (*CCache, error) {
- c := new(CCache)
- b, err := os.ReadFile(cpath)
- if err != nil {
- return c, err
- }
- err = c.Unmarshal(b)
- return c, err
- }
- // Unmarshal a byte slice of credential cache data into CCache type.
- func (c *CCache) Unmarshal(b []byte) error {
- p := 0
- //The first byte of the file always has the value 5
- if int8(b[p]) != 5 {
- return errors.New("Invalid credential cache data. First byte does not equal 5")
- }
- p++
- //Get credential cache version
- //The second byte contains the version number (1 to 4)
- c.Version = b[p]
- if c.Version < 1 || c.Version > 4 {
- return errors.New("Invalid credential cache data. Keytab version is not within 1 to 4")
- }
- p++
- //Version 1 or 2 of the file format uses native byte order for integer representations. Versions 3 & 4 always uses big-endian byte order
- var endian binary.ByteOrder
- endian = binary.BigEndian
- if (c.Version == 1 || c.Version == 2) && isNativeEndianLittle() {
- endian = binary.LittleEndian
- }
- if c.Version == 4 {
- err := parseHeader(b, &p, c, &endian)
- if err != nil {
- return err
- }
- }
- c.DefaultPrincipal = parsePrincipal(b, &p, c, &endian)
- for p < len(b) {
- cred, err := parseCredential(b, &p, c, &endian)
- if err != nil {
- return err
- }
- c.Credentials = append(c.Credentials, cred)
- }
- return nil
- }
- func parseHeader(b []byte, p *int, c *CCache, e *binary.ByteOrder) error {
- if c.Version != 4 {
- return errors.New("Credentials cache version is not 4 so there is no header to parse.")
- }
- h := header{}
- h.length = uint16(readInt16(b, p, e))
- for *p <= int(h.length) {
- f := headerField{}
- f.tag = uint16(readInt16(b, p, e))
- f.length = uint16(readInt16(b, p, e))
- f.value = b[*p : *p+int(f.length)]
- *p += int(f.length)
- if !f.valid() {
- return errors.New("Invalid credential cache header found")
- }
- h.fields = append(h.fields, f)
- }
- c.Header = h
- return nil
- }
- // Parse the Keytab bytes of a principal into a Keytab entry's principal.
- func parsePrincipal(b []byte, p *int, c *CCache, e *binary.ByteOrder) (princ principal) {
- if c.Version != 1 {
- //Name Type is omitted in version 1
- princ.PrincipalName.NameType = readInt32(b, p, e)
- }
- nc := int(readInt32(b, p, e))
- if c.Version == 1 {
- //In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
- nc--
- }
- lenRealm := readInt32(b, p, e)
- princ.Realm = string(readBytes(b, p, int(lenRealm), e))
- for i := 0; i < nc; i++ {
- l := readInt32(b, p, e)
- princ.PrincipalName.NameString = append(princ.PrincipalName.NameString, string(readBytes(b, p, int(l), e)))
- }
- return princ
- }
- func parseCredential(b []byte, p *int, c *CCache, e *binary.ByteOrder) (cred *Credential, err error) {
- cred = new(Credential)
- cred.Client = parsePrincipal(b, p, c, e)
- cred.Server = parsePrincipal(b, p, c, e)
- key := types.EncryptionKey{}
- key.KeyType = int32(readInt16(b, p, e))
- if c.Version == 3 {
- //repeated twice in version 3
- key.KeyType = int32(readInt16(b, p, e))
- }
- key.KeyValue = readData(b, p, e)
- cred.Key = key
- cred.AuthTime = readTimestamp(b, p, e)
- cred.StartTime = readTimestamp(b, p, e)
- cred.EndTime = readTimestamp(b, p, e)
- cred.RenewTill = readTimestamp(b, p, e)
- if ik := readInt8(b, p, e); ik == 0 {
- cred.IsSKey = false
- } else {
- cred.IsSKey = true
- }
- cred.TicketFlags = types.NewKrbFlags()
- cred.TicketFlags.Bytes = readBytes(b, p, 4, e)
- l := int(readInt32(b, p, e))
- cred.Addresses = make([]types.HostAddress, l, l)
- for i := range cred.Addresses {
- cred.Addresses[i] = readAddress(b, p, e)
- }
- l = int(readInt32(b, p, e))
- cred.AuthData = make([]types.AuthorizationDataEntry, l, l)
- for i := range cred.AuthData {
- cred.AuthData[i] = readAuthDataEntry(b, p, e)
- }
- cred.Ticket = readData(b, p, e)
- cred.SecondTicket = readData(b, p, e)
- return
- }
- // GetClientPrincipalName returns a PrincipalName type for the client the credentials cache is for.
- func (c *CCache) GetClientPrincipalName() types.PrincipalName {
- return c.DefaultPrincipal.PrincipalName
- }
- // GetClientRealm returns the reals of the client the credentials cache is for.
- func (c *CCache) GetClientRealm() string {
- return c.DefaultPrincipal.Realm
- }
- // GetClientCredentials returns a Credentials object representing the client of the credentials cache.
- func (c *CCache) GetClientCredentials() *Credentials {
- return &Credentials{
- username: c.DefaultPrincipal.PrincipalName.PrincipalNameString(),
- realm: c.GetClientRealm(),
- cname: c.DefaultPrincipal.PrincipalName,
- }
- }
- // Contains tests if the cache contains a credential for the provided server PrincipalName
- func (c *CCache) Contains(p types.PrincipalName) bool {
- for _, cred := range c.Credentials {
- if cred.Server.PrincipalName.Equal(p) {
- return true
- }
- }
- return false
- }
- // GetEntry returns a specific credential for the PrincipalName provided.
- func (c *CCache) GetEntry(p types.PrincipalName) (*Credential, bool) {
- cred := new(Credential)
- var found bool
- for i := range c.Credentials {
- if c.Credentials[i].Server.PrincipalName.Equal(p) {
- cred = c.Credentials[i]
- found = true
- break
- }
- }
- if !found {
- return cred, false
- }
- return cred, true
- }
- // GetEntries filters out configuration entries an returns a slice of credentials.
- func (c *CCache) GetEntries() []*Credential {
- creds := make([]*Credential, 0)
- for _, cred := range c.Credentials {
- // Filter out configuration entries
- if strings.HasPrefix(cred.Server.Realm, "X-CACHECONF") {
- continue
- }
- creds = append(creds, cred)
- }
- return creds
- }
- func (h *headerField) valid() bool {
- // See https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html - Header format
- switch h.tag {
- case headerFieldTagKDCOffset:
- if h.length != 8 || len(h.value) != 8 {
- return false
- }
- return true
- }
- return false
- }
- func readData(b []byte, p *int, e *binary.ByteOrder) []byte {
- l := readInt32(b, p, e)
- return readBytes(b, p, int(l), e)
- }
- func readAddress(b []byte, p *int, e *binary.ByteOrder) types.HostAddress {
- a := types.HostAddress{}
- a.AddrType = int32(readInt16(b, p, e))
- a.Address = readData(b, p, e)
- return a
- }
- func readAuthDataEntry(b []byte, p *int, e *binary.ByteOrder) types.AuthorizationDataEntry {
- a := types.AuthorizationDataEntry{}
- a.ADType = int32(readInt16(b, p, e))
- a.ADData = readData(b, p, e)
- return a
- }
- // Read bytes representing a timestamp.
- func readTimestamp(b []byte, p *int, e *binary.ByteOrder) time.Time {
- return time.Unix(int64(readInt32(b, p, e)), 0)
- }
- // Read bytes representing an eight bit integer.
- func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8) {
- buf := bytes.NewBuffer(b[*p : *p+1])
- binary.Read(buf, *e, &i)
- *p++
- return
- }
- // Read bytes representing a sixteen bit integer.
- func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16) {
- buf := bytes.NewBuffer(b[*p : *p+2])
- binary.Read(buf, *e, &i)
- *p += 2
- return
- }
- // Read bytes representing a thirty two bit integer.
- func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32) {
- buf := bytes.NewBuffer(b[*p : *p+4])
- binary.Read(buf, *e, &i)
- *p += 4
- return
- }
- func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) []byte {
- buf := bytes.NewBuffer(b[*p : *p+s])
- r := make([]byte, s)
- binary.Read(buf, *e, &r)
- *p += s
- return r
- }
- func isNativeEndianLittle() bool {
- var x = 0x012345678
- var p = unsafe.Pointer(&x)
- var bp = (*[4]byte)(p)
- var endian bool
- if 0x01 == bp[0] {
- endian = false
- } else if (0x78 & 0xff) == (bp[0] & 0xff) {
- endian = true
- } else {
- // Default to big endian
- endian = false
- }
- return endian
- }
|