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  }