Browse Source

marshal keytab to bytes and write to io.Writer

Jonathan Turner 8 years ago
parent
commit
a27c89a370
2 changed files with 139 additions and 13 deletions
  1. 114 3
      keytab/keytab.go
  2. 25 10
      keytab/keytab_test.go

+ 114 - 3
keytab/keytab.go

@@ -7,14 +7,19 @@ import (
 	"errors"
 	"fmt"
 	"gopkg.in/jcmturner/gokrb5.v4/types"
+	"io"
 	"io/ioutil"
 	"time"
 	"unsafe"
 )
 
+const (
+	keytabFirstByte byte = 05
+)
+
 // Keytab struct.
 type Keytab struct {
-	Version uint16
+	Version uint8
 	Entries []entry
 }
 
@@ -103,6 +108,26 @@ func Load(ktPath string) (kt Keytab, err error) {
 	return Parse(k)
 }
 
+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)
+}
+
 // Parse byte slice of Keytab data into Keytab type.
 func Parse(b []byte) (kt Keytab, err error) {
 	//The first byte of the file always has the value 5
@@ -111,8 +136,8 @@ func Parse(b []byte) (kt Keytab, err error) {
 		return
 	}
 	//Get keytab version
-	//The second byte contains the version number (1 or 2)
-	kt.Version = uint16(b[1])
+	//The 2nd byte contains the version number (1 or 2)
+	kt.Version = uint8(b[1])
 	if kt.Version != 1 && kt.Version != 2 {
 		err = errors.New("Invalid keytab data. Keytab version is neither 1 nor 2")
 		return
@@ -174,6 +199,45 @@ func Parse(b []byte) (kt Keytab, err error) {
 	return
 }
 
+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] = byte(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...)
+
+	// Add the length header
+	t = make([]byte, 4)
+	endian.PutUint32(t, uint32(len(b)))
+	b = append(t, b...)
+	return b, nil
+}
+
 // Parse the Keytab bytes of a principal into a Keytab entry's principal.
 func parsePrincipal(b []byte, p *int, kt *Keytab, ke *entry, e *binary.ByteOrder) (err error) {
 	ke.Principal.NumComponents = readInt16(b, p, e)
@@ -194,6 +258,53 @@ func parsePrincipal(b []byte, p *int, kt *Keytab, ke *entry, e *binary.ByteOrder
 	return
 }
 
+func (p principal) marshal(v int) ([]byte, error) {
+	//var b []byte
+	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
+}
+
 // 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)

+ 25 - 10
keytab/keytab_test.go

@@ -3,27 +3,42 @@ package keytab
 import (
 	"encoding/hex"
 	"github.com/stretchr/testify/assert"
+	"gopkg.in/jcmturner/gokrb5.v4/testdata"
 	"testing"
 	"time"
 )
 
-//Keytab data generated from ktutil
-const keytabDataHexStr = "0502000000320001000b4558414d504c452e434f4d00047573657200000001586aa82d01001700100c61039f010b2fbb88fe449fbf262477000000420001000b4558414d504c452e434f4d00047573657200000001586aa82d010012002053142f614ee6c39823710d9f31ff2984ed0bd9074d6e542e8468137f7b909c17000000320001000b4558414d504c452e434f4d00047573657200000001586beaad01001700100c61039f010b2fbb88fe449fbf262477000000420001000b4558414d504c452e434f4d00047573657200000001586beaae010012002053142f614ee6c39823710d9f31ff2984ed0bd9074d6e542e8468137f7b909c17000000430001000b4a544c414e2e434f2e554b000562696c6c7900000001586beaae1f00120020508dd2b209064e101bf209caef5fda236875706a5e9ad47c157db5907778785f"
-
 func TestParse(t *testing.T) {
-	dat, _ := hex.DecodeString(keytabDataHexStr)
+	dat, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
 	kt, err := Parse(dat)
 	if err != nil {
 		t.Fatalf("Error parsing keytab data: %v\n", err)
 	}
-	assert.Equal(t, uint16(2), kt.Version, "Keytab version not as expected")
+	assert.Equal(t, uint8(2), kt.Version, "Keytab version not as expected")
 	assert.Equal(t, uint32(1), kt.Entries[0].KVNO, "KVNO not as expected")
 	assert.Equal(t, uint8(1), kt.Entries[0].KVNO8, "KVNO8 not as expected")
-	assert.Equal(t, time.Unix(1483384877, 0), kt.Entries[0].Timestamp, "Timestamp not as expected")
-	assert.Equal(t, int32(23), kt.Entries[0].Key.KeyType, "Key's EType not as expected")
-	assert.Equal(t, "0c61039f010b2fbb88fe449fbf262477", hex.EncodeToString(kt.Entries[0].Key.KeyValue), "Key material not as expected")
+	assert.Equal(t, time.Unix(1505669592, 0), kt.Entries[0].Timestamp, "Timestamp not as expected")
+	assert.Equal(t, int32(17), kt.Entries[0].Key.KeyType, "Key's EType not as expected")
+	assert.Equal(t, "698c4df8e9f60e7eea5a21bf4526ad25", hex.EncodeToString(kt.Entries[0].Key.KeyValue), "Key material not as expected")
 	assert.Equal(t, int16(1), kt.Entries[0].Principal.NumComponents, "Number of components in principal not as expected")
 	assert.Equal(t, int32(1), kt.Entries[0].Principal.NameType, "Name type of principal not as expected")
-	assert.Equal(t, "EXAMPLE.COM", kt.Entries[0].Principal.Realm, "Realm of principal not as expected")
-	assert.Equal(t, "user", kt.Entries[0].Principal.Components[0], "Component in principal not as expected")
+	assert.Equal(t, "TEST.GOKRB5", kt.Entries[0].Principal.Realm, "Realm of principal not as expected")
+	assert.Equal(t, "testuser1", kt.Entries[0].Principal.Components[0], "Component in principal not as expected")
+}
+
+func TestMarshal(t *testing.T) {
+	dat, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
+	kt, err := Parse(dat)
+	if err != nil {
+		t.Fatalf("Error parsing keytab data: %v\n", err)
+	}
+	b, err := kt.marshal()
+	if err != nil {
+		t.Fatalf("Error marshaling: %v", err)
+	}
+	assert.Equal(t, dat, b, "Marshaled bytes not the same as input bytes")
+	_, err = Parse(b)
+	if err != nil {
+		t.Fatalf("Error parsing marshaled bytes: %v", err)
+	}
 }