mirror of
https://github.com/kubenetworks/kubevpn.git
synced 2025-09-27 03:36:09 +08:00
669 lines
15 KiB
Go
669 lines
15 KiB
Go
package ssh
|
|
|
|
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
|
|
}
|
|
|
|
// NewV4CCache creates a new Version 4 CCache with no credentials in it
|
|
func NewV4CCache() *CCache {
|
|
c := CCache{}
|
|
c.Version = 4
|
|
h := header{}
|
|
h.length = 0
|
|
c.Header = h
|
|
return &c
|
|
}
|
|
|
|
// AddCredential adds a credential to a CCache
|
|
func (c *CCache) AddCredential(cred *Credential) {
|
|
c.Credentials = append(c.Credentials, cred)
|
|
}
|
|
|
|
// SetDefaultPrincipal sets the default principal for a CCache
|
|
func (c *CCache) SetDefaultPrincipal(d principal) {
|
|
c.DefaultPrincipal = d
|
|
}
|
|
|
|
// NewPrincipal returns a new principal
|
|
func NewPrincipal(name types.PrincipalName, realm string) principal {
|
|
p := principal{}
|
|
p.Realm = realm
|
|
p.PrincipalName = name
|
|
return p
|
|
}
|
|
|
|
// 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++
|
|
endian := c.getEndian()
|
|
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
|
|
}
|
|
|
|
// Marshal a CCache type into a byte string
|
|
func (c *CCache) Marshal() ([]byte, error) {
|
|
var b bytes.Buffer
|
|
var err error
|
|
endian := c.getEndian()
|
|
|
|
// The first byte of the file always has the value 5
|
|
err = b.WriteByte(5)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Write the CCache version
|
|
err = b.WriteByte(c.Version)
|
|
|
|
if c.Version == 4 {
|
|
// Write version 4 header
|
|
headerBytes, err := c.writeV4Header()
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
_, err = b.Write(headerBytes)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
// Write default principal
|
|
princBytes, err := c.writePrincipal(c.DefaultPrincipal, endian)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
_, err = b.Write(princBytes)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Write credentials
|
|
for _, cred := range c.Credentials {
|
|
credBytes, err := c.writeCredential(cred, endian)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
_, err = b.Write(credBytes)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
func (c *CCache) writeV4Header() ([]byte, error) {
|
|
var byteString bytes.Buffer
|
|
var err error
|
|
|
|
b := &byteString
|
|
|
|
// V4 is always BigEndian
|
|
endian := binary.BigEndian
|
|
|
|
// Write header length
|
|
err = binary.Write(b, endian, c.Header.length)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
for _, field := range c.Header.fields {
|
|
// Write field tag
|
|
err = binary.Write(b, endian, field.tag)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Write field length
|
|
err = binary.Write(b, endian, field.length)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Write field data
|
|
_, err = b.Write(field.value)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
return byteString.Bytes(), nil
|
|
}
|
|
|
|
func (c *CCache) writePrincipal(p principal, endian *binary.ByteOrder) ([]byte, error) {
|
|
var byteString bytes.Buffer
|
|
var err error
|
|
|
|
b := &byteString
|
|
|
|
// Version 1 does not have the name type
|
|
if c.Version != 1 {
|
|
err = binary.Write(b, *endian, uint32(p.PrincipalName.NameType))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
// Count of components
|
|
componentCount := len(p.PrincipalName.NameString)
|
|
if c.Version == 1 {
|
|
// Version 1 includes realm in count
|
|
componentCount = componentCount + 1
|
|
}
|
|
err = binary.Write(b, *endian, uint32(componentCount))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Realm --- Length then data
|
|
|
|
realmLength := len(p.Realm)
|
|
err = binary.Write(b, *endian, uint32(realmLength))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
_, err = b.WriteString(p.Realm)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Components
|
|
for _, namePart := range p.PrincipalName.NameString {
|
|
// length then data
|
|
err = binary.Write(b, *endian, uint32(len(namePart)))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
_, err = b.WriteString(namePart)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
return byteString.Bytes(), nil
|
|
|
|
}
|
|
|
|
func (c *CCache) writeCredential(cred *Credential, endian *binary.ByteOrder) ([]byte, error) {
|
|
var byteString bytes.Buffer
|
|
var err error
|
|
|
|
b := &byteString
|
|
|
|
// Client - a principal
|
|
princBytes, err := c.writePrincipal(cred.Client, endian)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
_, err = b.Write(princBytes)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Server - a principal
|
|
princBytes, err = c.writePrincipal(cred.Server, endian)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
_, err = b.Write(princBytes)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Key - 16 bit key type, then key data
|
|
err = binary.Write(b, *endian, uint16(cred.Key.KeyType))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
if c.Version == 3 {
|
|
// Version 3 repeats the key type for some reason
|
|
err = binary.Write(b, *endian, uint32(cred.Key.KeyType))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
keyLen := len(cred.Key.KeyValue)
|
|
err = binary.Write(b, *endian, uint32(keyLen))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
_, err = b.Write(cred.Key.KeyValue)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// AuthTime, StartTime, EndTime, RewnewTil - all 32 bit
|
|
// Unix Epoch seconds
|
|
for _, timeValue := range []time.Time{cred.AuthTime, cred.StartTime, cred.EndTime, cred.RenewTill} {
|
|
err = binary.Write(b, *endian, uint32(timeValue.Unix()))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
// IsSKey
|
|
isSKey := uint8(0)
|
|
if cred.IsSKey {
|
|
isSKey = uint8(1)
|
|
}
|
|
err = binary.Write(b, *endian, isSKey)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// TicketFlags
|
|
err = binary.Write(b, *endian, cred.TicketFlags.Bytes)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Addresses
|
|
// Address count first
|
|
err = binary.Write(b, *endian, uint32(len(cred.Addresses)))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Then each address
|
|
for _, address := range cred.Addresses {
|
|
// Type
|
|
err = binary.Write(b, *endian, uint16(address.AddrType))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
// Data length
|
|
err = binary.Write(b, *endian, uint32(len(address.Address)))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
// Data
|
|
_, err = b.Write(address.Address)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
// AuthData
|
|
// AuthData count first
|
|
err = binary.Write(b, *endian, uint32(len(cred.AuthData)))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Then each auth data
|
|
for _, authData := range cred.AuthData {
|
|
// Type
|
|
err = binary.Write(b, *endian, uint16(authData.ADType))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
// Data length
|
|
err = binary.Write(b, *endian, uint32(len(authData.ADData)))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
// Data
|
|
_, err = b.Write(authData.ADData)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
}
|
|
|
|
// Ticket
|
|
err = binary.Write(b, *endian, uint32(len(cred.Ticket)))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
_, err = b.Write(cred.Ticket)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
// Second Ticket
|
|
err = binary.Write(b, *endian, uint32(len(cred.SecondTicket)))
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
_, err = b.Write(cred.SecondTicket)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
return byteString.Bytes(), nil
|
|
}
|
|
|
|
// Return either binary.ByteOrder depending on the CCache
|
|
// version and machine endianess
|
|
func (c *CCache) getEndian() *binary.ByteOrder {
|
|
var endian binary.ByteOrder
|
|
endian = binary.BigEndian
|
|
//Version 1 or 2 of the file format uses native byte order for integer representations. Versions 3 & 4 always uses big-endian byte order
|
|
if (c.Version == 1 || c.Version == 2) && isNativeEndianLittle() {
|
|
endian = binary.LittleEndian
|
|
}
|
|
|
|
return &endian
|
|
}
|
|
|
|
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
|
|
}
|