package keytab
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"
"time"
"unsafe"
"github.com/jcmturner/gokrb5/v8/crypto"
"github.com/jcmturner/gokrb5/v8/types"
)
const (
keytabFirstByte byte = 05
)
type Keytab struct {
version uint8
Entries []entry
}
type entry struct {
Principal principal
Timestamp time .Time
KVNO8 uint8
Key types .EncryptionKey
KVNO uint32
}
func (e entry ) String () string {
return fmt .Sprintf ("% 4d %s %-56s %2d %-64x" ,
e .KVNO8 ,
e .Timestamp .Format ("02/01/06 15:04:05" ),
e .Principal .String (),
e .Key .KeyType ,
e .Key .KeyValue ,
)
}
type principal struct {
NumComponents int16 `json:"-"`
Realm string
Components []string
NameType int32
}
func (p principal ) String () string {
return fmt .Sprintf ("%s@%s" , strings .Join (p .Components , "/" ), p .Realm )
}
func New () *Keytab {
var e []entry
return &Keytab {
version : 2 ,
Entries : e ,
}
}
func (kt *Keytab ) GetEncryptionKey (princName types .PrincipalName , realm string , kvno int , etype int32 ) (types .EncryptionKey , int , error ) {
var key types .EncryptionKey
var t time .Time
var kv int
for _ , k := range kt .Entries {
if k .Principal .Realm == realm && len (k .Principal .Components ) == len (princName .NameString ) &&
k .Key .KeyType == etype &&
(k .KVNO == uint32 (kvno ) || kvno == 0 ) &&
k .Timestamp .After (t ) {
p := true
for i , n := range k .Principal .Components {
if princName .NameString [i ] != n {
p = false
break
}
}
if p {
key = k .Key
kv = int (k .KVNO )
t = k .Timestamp
}
}
}
if len (key .KeyValue ) < 1 {
return key , 0 , fmt .Errorf ("matching key not found in keytab. Looking for %q realm: %v kvno: %v etype: %v" , princName .PrincipalNameString (), realm , kvno , etype )
}
return key , kv , nil
}
func newEntry() entry {
var b []byte
return entry {
Principal : newPrincipal (),
Timestamp : time .Time {},
KVNO8 : 0 ,
Key : types .EncryptionKey {
KeyType : 0 ,
KeyValue : b ,
},
KVNO : 0 ,
}
}
func (kt Keytab ) String () string {
var s string
s = `KVNO Timestamp Principal ET Key
---- ----------------- -------------------------------------------------------- -- ----------------------------------------------------------------
`
for _ , entry := range kt .Entries {
s += entry .String () + "\n"
}
return s
}
func (kt *Keytab ) AddEntry (principalName , realm , password string , ts time .Time , KVNO uint8 , encType int32 ) error {
princ , _ := types .ParseSPNString (principalName )
key , _ , err := crypto .GetKeyFromPassword (password , princ , realm , encType , types .PADataSequence {})
if err != nil {
return err
}
ktep := newPrincipal ()
ktep .NumComponents = int16 (len (princ .NameString ))
if kt .version == 1 {
ktep .NumComponents += 1
}
ktep .Realm = realm
ktep .Components = princ .NameString
ktep .NameType = princ .NameType
e := newEntry ()
e .Principal = ktep
e .Timestamp = ts
e .KVNO8 = KVNO
e .KVNO = uint32 (KVNO )
e .Key = key
kt .Entries = append (kt .Entries , e )
return nil
}
func newPrincipal() principal {
var c []string
return principal {
NumComponents : 0 ,
Realm : "" ,
Components : c ,
NameType : 0 ,
}
}
func Load (ktPath string ) (*Keytab , error ) {
kt := new (Keytab )
b , err := os .ReadFile (ktPath )
if err != nil {
return kt , err
}
err = kt .Unmarshal (b )
return kt , err
}
func (kt *Keytab ) Marshal () ([]byte , error ) {
b := []byte {keytabFirstByte , kt .version }
for _ , e := range kt .Entries {
eb , err := e .marshal (int (kt .version ))
if err != nil {
return b , err
}
b = append (b , eb ...)
}
return b , nil
}
func (kt *Keytab ) Write (w io .Writer ) (int , error ) {
b , err := kt .Marshal ()
if err != nil {
return 0 , fmt .Errorf ("error marshaling keytab: %v" , err )
}
return w .Write (b )
}
func (kt *Keytab ) Unmarshal (b []byte ) error {
if len (b ) < 2 {
return fmt .Errorf ("byte array is less than 2 bytes: %d" , len (b ))
}
if b [0 ] != keytabFirstByte {
return errors .New ("invalid keytab data. First byte does not equal 5" )
}
kt .version = b [1 ]
if kt .version != 1 && kt .version != 2 {
return errors .New ("invalid keytab data. Keytab version is neither 1 nor 2" )
}
var endian binary .ByteOrder
endian = binary .BigEndian
if kt .version == 1 && isNativeEndianLittle () {
endian = binary .LittleEndian
}
n := 2
l , err := readInt32 (b , &n , &endian )
if err != nil {
return err
}
for l != 0 {
if l < 0 {
l = l * -1
n = n + int (l )
} else {
if n < 0 {
return fmt .Errorf ("%d can't be less than zero" , n )
}
if n +int (l ) > len (b ) {
return fmt .Errorf ("%s's length is less than %d" , b , n +int (l ))
}
eb := b [n : n +int (l )]
n = n + int (l )
ke := newEntry ()
var p int
var err error
parsePrincipal (eb , &p , kt , &ke , &endian )
ke .Timestamp , err = readTimestamp (eb , &p , &endian )
if err != nil {
return err
}
rei8 , err := readInt8 (eb , &p , &endian )
if err != nil {
return err
}
ke .KVNO8 = uint8 (rei8 )
rei16 , err := readInt16 (eb , &p , &endian )
if err != nil {
return err
}
ke .Key .KeyType = int32 (rei16 )
rei16 , err = readInt16 (eb , &p , &endian )
if err != nil {
return err
}
kl := int (rei16 )
ke .Key .KeyValue , err = readBytes (eb , &p , kl , &endian )
if err != nil {
return err
}
if len (eb )-p >= 4 {
ri32 , err := readInt32 (eb , &p , &endian )
if err != nil {
return err
}
ke .KVNO = uint32 (ri32 )
}
if ke .KVNO == 0 {
ke .KVNO = uint32 (ke .KVNO8 )
}
kt .Entries = append (kt .Entries , ke )
}
if n < 0 || n > len (b ) || len (b [n :]) < 4 {
break
}
l , err = readInt32 (b , &n , &endian )
if err != nil {
return err
}
}
return nil
}
func (e entry ) marshal (v int ) ([]byte , error ) {
var b []byte
pb , err := e .Principal .marshal (v )
if err != nil {
return b , err
}
b = append (b , pb ...)
var endian binary .ByteOrder
endian = binary .BigEndian
if v == 1 && isNativeEndianLittle () {
endian = binary .LittleEndian
}
t := make ([]byte , 9 )
endian .PutUint32 (t [0 :4 ], uint32 (e .Timestamp .Unix ()))
t [4 ] = e .KVNO8
endian .PutUint16 (t [5 :7 ], uint16 (e .Key .KeyType ))
endian .PutUint16 (t [7 :9 ], uint16 (len (e .Key .KeyValue )))
b = append (b , t ...)
buf := new (bytes .Buffer )
err = binary .Write (buf , endian , e .Key .KeyValue )
if err != nil {
return b , err
}
b = append (b , buf .Bytes ()...)
t = make ([]byte , 4 )
endian .PutUint32 (t , e .KVNO )
b = append (b , t ...)
t = make ([]byte , 4 )
endian .PutUint32 (t , uint32 (len (b )))
b = append (t , b ...)
return b , nil
}
func parsePrincipal(b []byte , p *int , kt *Keytab , ke *entry , e *binary .ByteOrder ) error {
var err error
ke .Principal .NumComponents , err = readInt16 (b , p , e )
if err != nil {
return err
}
if kt .version == 1 {
ke .Principal .NumComponents --
}
lenRealm , err := readInt16 (b , p , e )
if err != nil {
return err
}
realmB , err := readBytes (b , p , int (lenRealm ), e )
if err != nil {
return err
}
ke .Principal .Realm = string (realmB )
for i := 0 ; i < int (ke .Principal .NumComponents ); i ++ {
l , err := readInt16 (b , p , e )
if err != nil {
return err
}
compB , err := readBytes (b , p , int (l ), e )
if err != nil {
return err
}
ke .Principal .Components = append (ke .Principal .Components , string (compB ))
}
if kt .version != 1 {
ke .Principal .NameType , err = readInt32 (b , p , e )
if err != nil {
return err
}
}
return nil
}
func (p principal ) marshal (v int ) ([]byte , error ) {
b := make ([]byte , 2 )
var endian binary .ByteOrder
endian = binary .BigEndian
if v == 1 && isNativeEndianLittle () {
endian = binary .LittleEndian
}
endian .PutUint16 (b [0 :], uint16 (p .NumComponents ))
realm , err := marshalString (p .Realm , v )
if err != nil {
return b , err
}
b = append (b , realm ...)
for _ , c := range p .Components {
cb , err := marshalString (c , v )
if err != nil {
return b , err
}
b = append (b , cb ...)
}
if v != 1 {
t := make ([]byte , 4 )
endian .PutUint32 (t , uint32 (p .NameType ))
b = append (b , t ...)
}
return b , nil
}
func marshalString(s string , v int ) ([]byte , error ) {
sb := []byte (s )
b := make ([]byte , 2 )
var endian binary .ByteOrder
endian = binary .BigEndian
if v == 1 && isNativeEndianLittle () {
endian = binary .LittleEndian
}
endian .PutUint16 (b [0 :], uint16 (len (sb )))
buf := new (bytes .Buffer )
err := binary .Write (buf , endian , sb )
if err != nil {
return b , err
}
b = append (b , buf .Bytes ()...)
return b , err
}
func readTimestamp(b []byte , p *int , e *binary .ByteOrder ) (time .Time , error ) {
i32 , err := readInt32 (b , p , e )
if err != nil {
return time .Time {}, err
}
return time .Unix (int64 (i32 ), 0 ), nil
}
func readInt8(b []byte , p *int , e *binary .ByteOrder ) (i int8 , err error ) {
if *p < 0 {
return 0 , fmt .Errorf ("%d cannot be less than zero" , *p )
}
if (*p + 1 ) > len (b ) {
return 0 , fmt .Errorf ("%s's length is less than %d" , b , *p +1 )
}
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 , err error ) {
if *p < 0 {
return 0 , fmt .Errorf ("%d cannot be less than zero" , *p )
}
if (*p + 2 ) > len (b ) {
return 0 , fmt .Errorf ("%s's length is less than %d" , b , *p +2 )
}
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 , err error ) {
if *p < 0 {
return 0 , fmt .Errorf ("%d cannot be less than zero" , *p )
}
if (*p + 4 ) > len (b ) {
return 0 , fmt .Errorf ("%s's length is less than %d" , b , *p +4 )
}
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 , error ) {
if s < 0 {
return nil , fmt .Errorf ("%d cannot be less than zero" , s )
}
i := *p + s
if i > len (b ) {
return nil , fmt .Errorf ("%s's length is greater than %d" , b , i )
}
buf := bytes .NewBuffer (b [*p :i ])
r := make ([]byte , s )
if err := binary .Read (buf , *e , &r ); err != nil {
return nil , err
}
*p += s
return r , nil
}
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
}
func (kt *Keytab ) JSON () (string , error ) {
b , err := json .MarshalIndent (kt , "" , " " )
if err != nil {
return "" , err
}
return string (b ), nil
}
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 .