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
)
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
}
type principal struct {
Realm string
PrincipalName types .PrincipalName
}
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
}
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
}
func (c *CCache ) Unmarshal (b []byte ) error {
p := 0
if int8 (b [p ]) != 5 {
return errors .New ("Invalid credential cache data. First byte does not equal 5" )
}
p ++
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 ++
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
}
func parsePrincipal(b []byte , p *int , c *CCache , e *binary .ByteOrder ) (princ principal ) {
if c .Version != 1 {
princ .PrincipalName .NameType = readInt32 (b , p , e )
}
nc := int (readInt32 (b , p , e ))
if c .Version == 1 {
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 {
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
}
func (c *CCache ) GetClientPrincipalName () types .PrincipalName {
return c .DefaultPrincipal .PrincipalName
}
func (c *CCache ) GetClientRealm () string {
return c .DefaultPrincipal .Realm
}
func (c *CCache ) GetClientCredentials () *Credentials {
return &Credentials {
username : c .DefaultPrincipal .PrincipalName .PrincipalNameString (),
realm : c .GetClientRealm (),
cname : c .DefaultPrincipal .PrincipalName ,
}
}
func (c *CCache ) Contains (p types .PrincipalName ) bool {
for _ , cred := range c .Credentials {
if cred .Server .PrincipalName .Equal (p ) {
return true
}
}
return false
}
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
}
func (c *CCache ) GetEntries () []*Credential {
creds := make ([]*Credential , 0 )
for _ , cred := range c .Credentials {
if strings .HasPrefix (cred .Server .Realm , "X-CACHECONF" ) {
continue
}
creds = append (creds , cred )
}
return creds
}
func (h *headerField ) valid () bool {
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
}
func readTimestamp(b []byte , p *int , e *binary .ByteOrder ) time .Time {
return time .Unix (int64 (readInt32 (b , p , e )), 0 )
}
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
}
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
}
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 {
endian = false
}
return endian
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .