Просмотр исходного кода

fixes to PAC decoding

* try fix for client claims info

* try fix for client claims info

* update device claims info

* cred info not ndr

* test data for client claims info

* wip claims

* claims info fixes

* fix null string panic

* cred ver check

* fix error on CredentialsInfo in the PAC

* wip

* multi claim entries

* multi claim entries

* convert to external ndr project

* convert to external ndr project

* fix handling of varargs in Errorf()

Fixes https://github.com/jcmturner/gokrb5/issues/162

Re-use Add() function that does what we need; pass varargs as a...

Also remove special-casing of empty varargs to aid readability, and
add a test.

* print AS or TGS exchange errors from the KDC as "KDC_Error"

Fixes https://github.com/jcmturner/gokrb5/issues/161

We make an assumption that errors from SendToKDC() of type KRBError
come from the KDC.

* default action on kerb error response

* lint fix
Jonathan Turner 7 лет назад
Родитель
Сommit
1010d14c54

+ 43 - 0
client/client_ad_integration_test.go

@@ -106,3 +106,46 @@ func TestClient_GetServiceTicket_AD_TRUST_USER_DOMAIN(t *testing.T) {
 	assert.Equal(t, "testuser1", pac.KerbValidationInfo.EffectiveName.Value, "PAC value not parsed")
 
 }
+
+func TestClient_GetServiceTicket_AD_USER_DOMAIN(t *testing.T) {
+	b, _ := hex.DecodeString(testdata.TESTUSER1_USERKRB5_AD_KEYTAB)
+	kt, _ := keytab.Parse(b)
+	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
+	c.Realms[0].KDC = []string{testdata.TEST_KDC_AD_TRUST_USER_DOMAIN}
+	c.LibDefaults.DefaultRealm = "USER.GOKRB5"
+	c.LibDefaults.Canonicalize = true
+	cl := NewClientWithKeytab("testuser1", "USER.GOKRB5", kt)
+	c.LibDefaults.DefaultTktEnctypes = []string{"rc4-hmac"}
+	c.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]}
+	c.LibDefaults.DefaultTGSEnctypes = []string{"rc4-hmac"}
+	c.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeID.ETypesByName["rc4-hmac"]}
+	cl.WithConfig(c)
+	cl.GoKrb5Conf.DisablePAFXFast = true
+
+	err := cl.Login()
+
+	if err != nil {
+		t.Fatalf("Error on login: %v\n", err)
+	}
+	spn := "HTTP/user2.user.gokrb5"
+	tkt, _, err := cl.GetServiceTicket(spn)
+	if err != nil {
+		t.Fatalf("Error getting service ticket: %v\n", err)
+	}
+	assert.Equal(t, spn, tkt.SName.GetPrincipalNameString())
+	//assert.Equal(t, etypeID.ETypesByName["rc4-hmac"], key.KeyType)
+
+	b, _ = hex.DecodeString(testdata.TESTUSER2_USERKRB5_AD_KEYTAB)
+	skt, _ := keytab.Parse(b)
+	err = tkt.DecryptEncPart(skt, "testuser2")
+	if err != nil {
+		t.Errorf("error decrypting ticket with service keytab: %v", err)
+	}
+	isPAC, pac, err := tkt.GetPACType(skt, "testuser2")
+	if err != nil {
+		t.Errorf("error getting PAC: %v", err)
+	}
+	assert.True(t, isPAC, "Did not find PAC in service ticket")
+	assert.Equal(t, "testuser1", pac.KerbValidationInfo.EffectiveName.Value, "PAC value not parsed")
+
+}

+ 3 - 3
messages/Ticket.go

@@ -216,7 +216,7 @@ func (t *Ticket) DecryptEncPart(keytab keytab.Keytab, ktprinc string) error {
 }
 
 // GetPACType returns a Microsoft PAC that has been extracted from the ticket and processed.
-func (t *Ticket) GetPACType(keytab keytab.Keytab, sa string) (bool, pac.PACType, error) {
+func (t *Ticket) GetPACType(keytab keytab.Keytab, ktprinc string) (bool, pac.PACType, error) {
 	var isPAC bool
 	for _, ad := range t.DecryptedEncPart.AuthorizationData {
 		if ad.ADType == adtype.ADIfRelevant {
@@ -233,8 +233,8 @@ func (t *Ticket) GetPACType(keytab keytab.Keytab, sa string) (bool, pac.PACType,
 					return isPAC, p, fmt.Errorf("error unmarshaling PAC: %v", err)
 				}
 				var upn []string
-				if sa != "" {
-					upn = strings.Split(sa, "/")
+				if ktprinc != "" {
+					upn = strings.Split(ktprinc, "/")
 				} else {
 					upn = t.SName.NameString
 				}

+ 312 - 0
mstypes/claims.go

@@ -0,0 +1,312 @@
+package mstypes
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+
+	"gopkg.in/jcmturner/rpc.v0/ndr"
+)
+
+// Compression format assigned numbers.
+const (
+	CompressionFormatNone       uint16 = 0
+	CompressionFormatLZNT1      uint16 = 2
+	CompressionFormatXPress     uint16 = 3
+	CompressionFormatXPressHuff uint16 = 4
+)
+
+// ClaimsSourceType
+const ClaimsSourceTypeAD uint16 = 1
+
+// Claim Type assigned numbers
+const (
+	ClaimTypeIDInt64    uint16 = 1
+	ClaimTypeIDUInt64   uint16 = 2
+	ClaimTypeIDString   uint16 = 3
+	ClaimsTypeIDBoolean uint16 = 6
+)
+
+// ClaimsBlob implements https://msdn.microsoft.com/en-us/library/hh554119.aspx
+type ClaimsBlob struct {
+	Size        uint32
+	EncodedBlob []byte
+}
+
+// ReadClaimsBlob reads a ClaimsBlob from the byte slice.
+func ReadClaimsBlob(b *[]byte, p *int, e *binary.ByteOrder) (c ClaimsBlob) {
+	c.Size = ndr.ReadUint32(b, p, e)
+	c.EncodedBlob = ndr.ReadBytes(b, p, int(c.Size), e)
+	return
+}
+
+// ClaimsSetMetadata implements https://msdn.microsoft.com/en-us/library/hh554073.aspx
+type ClaimsSetMetadata struct {
+	claimsSetSize             uint32
+	ClaimsSet                 ClaimsSet
+	CompressionFormat         uint16 // Enum see constants for options
+	uncompressedClaimsSetSize uint32
+	ReservedType              uint16
+	reservedFieldSize         uint32
+	ReservedField             []byte
+}
+
+// ClaimSet implements https://msdn.microsoft.com/en-us/library/hh554122.aspx
+type ClaimsSet struct {
+	ClaimsArrayCount  uint32
+	ClaimsArrays      []ClaimsArray
+	ReservedType      uint16
+	reservedFieldSize uint32
+	ReservedField     []byte
+}
+
+// ClaimsArray implements https://msdn.microsoft.com/en-us/library/hh536458.aspx
+type ClaimsArray struct {
+	ClaimsSourceType uint16
+	ClaimsCount      uint32
+	ClaimsEntries    []ClaimEntry
+}
+
+// ClaimEntry implements https://msdn.microsoft.com/en-us/library/hh536374.aspx
+type ClaimEntry struct {
+	ID         string //utf16string
+	Type       uint16 // enums are 16 bit https://msdn.microsoft.com/en-us/library/windows/desktop/aa366818(v=vs.85).aspx
+	TypeInt64  ClaimTypeInt64
+	TypeUInt64 ClaimTypeUInt64
+	TypeString ClaimTypeString
+	TypeBool   ClaimTypeBoolean
+}
+
+// ClaimTypeInt64 is a claim of type int64
+type ClaimTypeInt64 struct {
+	ValueCount uint32
+	Value      []int64
+}
+
+// ClaimTypeUInt64 is a claim of type uint64
+type ClaimTypeUInt64 struct {
+	ValueCount uint32
+	Value      []uint64
+}
+
+// ClaimTypeString is a claim of type string
+type ClaimTypeString struct {
+	ValueCount uint32
+	Value      []string
+}
+
+// ClaimTypeBoolean is a claim of type bool
+type ClaimTypeBoolean struct {
+	ValueCount uint32
+	Value      []bool
+}
+
+// ReadClaimsSetMetadata reads a ClaimsSetMetadata from the bytes slice.
+func ReadClaimsSetMetadata(b *[]byte, p *int, e *binary.ByteOrder) (c ClaimsSetMetadata, err error) {
+	c.claimsSetSize = ndr.ReadUint32(b, p, e)
+	*p += 4 //Move over pointer to ClaimSet array
+	c.CompressionFormat = ndr.ReadUint16(b, p, e)
+	// TODO Currently compression is not supported so if it is compressed we just have to return.
+	if c.CompressionFormat != CompressionFormatNone {
+		*p = len(*b)
+		return
+	}
+	c.uncompressedClaimsSetSize = ndr.ReadUint32(b, p, e)
+	c.ReservedType = ndr.ReadUint16(b, p, e)
+	c.reservedFieldSize = ndr.ReadUint32(b, p, e)
+	*p += 4 //Move over pointer to ReservedField array
+	var ah ndr.ConformantArrayHeader
+	if c.claimsSetSize > 0 {
+		// ClaimsSet is a conformant array https://msdn.microsoft.com/en-us/library/windows/desktop/aa373603(v=vs.85).aspx
+		ah, err = ndr.ReadUniDimensionalConformantArrayHeader(b, p, e)
+		if err != nil {
+			return
+		}
+		if ah.MaxCount != int(c.claimsSetSize) {
+			err = errors.New("error with size of CLAIMS_SET array")
+			return
+		}
+		csb := ndr.ReadBytes(b, p, int(c.claimsSetSize), e)
+		//TODO put decompression here
+		c.ClaimsSet, err = ReadClaimsSet(csb)
+		if err != nil {
+			return
+		}
+	}
+	if c.reservedFieldSize > 0 {
+		ah, err = ndr.ReadUniDimensionalConformantArrayHeader(b, p, e)
+		if err != nil {
+			return
+		}
+		if ah.MaxCount != int(c.reservedFieldSize) {
+			err = errors.New("error with size of CLAIMS_SET_METADATA's reserved field array")
+			return
+		}
+		c.ReservedField = ndr.ReadBytes(b, p, int(c.reservedFieldSize), e)
+	}
+	return
+}
+
+// ReadClaimsSet reads a ClaimsSet from the bytes slice.
+func ReadClaimsSet(b []byte) (c ClaimsSet, err error) {
+	ch, _, p, err := ndr.ReadHeaders(&b)
+	if err != nil {
+		err = fmt.Errorf("error parsing NDR byte stream headers of CLAIMS_SET: %v", err)
+		return
+	}
+	e := &ch.Endianness
+	//The next 4 bytes are an RPC unique pointer referent. We just skip these
+	p += 4
+
+	c.ClaimsArrayCount = ndr.ReadUint32(&b, &p, e)
+	p += 4 //Move over pointer to claims array
+	c.ReservedType = ndr.ReadUint16(&b, &p, e)
+	c.reservedFieldSize = ndr.ReadUint32(&b, &p, e)
+	p += 4 //Move over pointer to ReservedField array
+
+	var ah ndr.ConformantArrayHeader
+	if c.ClaimsArrayCount > 0 {
+		ah, err = ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
+		if err != nil {
+			return
+		}
+		if ah.MaxCount != int(c.ClaimsArrayCount) {
+			err = errors.New("error with size of CLAIMS_SET's claims array")
+			return
+		}
+		c.ClaimsArrays = make([]ClaimsArray, c.ClaimsArrayCount, c.ClaimsArrayCount)
+		for i := range c.ClaimsArrays {
+			c.ClaimsArrays[i], err = ReadClaimsArray(&b, &p, e)
+			if err != nil {
+				return
+			}
+		}
+	}
+	if c.reservedFieldSize > 0 {
+		ah, err = ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
+		if err != nil {
+			return
+		}
+		if ah.MaxCount != int(c.reservedFieldSize) {
+			err = errors.New("error with size of CLAIMS_SET's reserved field array")
+			return
+		}
+		c.ReservedField = ndr.ReadBytes(&b, &p, int(c.reservedFieldSize), e)
+	}
+	return c, nil
+}
+
+// ReadClaimsArray reads a ClaimsArray from the bytes slice.
+func ReadClaimsArray(b *[]byte, p *int, e *binary.ByteOrder) (c ClaimsArray, err error) {
+	c.ClaimsSourceType = ndr.ReadUint16(b, p, e)
+	c.ClaimsCount = ndr.ReadUint32(b, p, e)
+	*p += 4 //Move over pointer to claims array
+	ah, err := ndr.ReadUniDimensionalConformantArrayHeader(b, p, e)
+	if err != nil {
+		return
+	}
+	if ah.MaxCount != int(c.ClaimsCount) {
+		err = errors.New("error with size of CLAIMS_ARRAY's claims entries")
+		return
+	}
+	c.ClaimsEntries = make([]ClaimEntry, c.ClaimsCount, c.ClaimsCount)
+	for i := range c.ClaimsEntries {
+		var vc uint32
+		c.ClaimsEntries[i].Type, vc, err = ReadClaimEntriesUnionHeaders(b, p, e)
+		if err != nil {
+			return
+		}
+		switch c.ClaimsEntries[i].Type {
+		case ClaimTypeIDInt64:
+			c.ClaimsEntries[i].TypeInt64.ValueCount = vc
+		case ClaimTypeIDUInt64:
+			c.ClaimsEntries[i].TypeUInt64.ValueCount = vc
+		case ClaimTypeIDString:
+			c.ClaimsEntries[i].TypeString.ValueCount = vc
+		case ClaimsTypeIDBoolean:
+			c.ClaimsEntries[i].TypeBool.ValueCount = vc
+		}
+	}
+	for i := range c.ClaimsEntries {
+		err = FillClaimEntry(b, p, e, &c.ClaimsEntries[i])
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+func ReadClaimEntriesUnionHeaders(b *[]byte, p *int, e *binary.ByteOrder) (uint16, uint32, error) {
+	*p += 4
+	// This is an NDR union: http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm#tagfcjh_39
+	// The discriminant [tag] is marshalled into the transmitted data stream twice:
+	// once as the field or parameter in the procedure argument list and
+	// once as the first part of the union representation [value]
+	t1 := ndr.ReadUint16(b, p, e)
+	t2 := ndr.ReadUint16(b, p, e)
+	if t1 != t2 {
+		return 0, 0, ndr.Malformed{EText: "malformed NDR encoding of CLAIM_ENTRY union"}
+	}
+	vc := ndr.ReadUint32(b, p, e)
+	*p += 4 //Move over pointer to array of values
+	return t1, vc, nil
+}
+
+// FillClaimEntry reads a ClaimEntry from the bytes slice.
+func FillClaimEntry(b *[]byte, p *int, e *binary.ByteOrder, c *ClaimEntry) (err error) {
+	c.ID, err = ndr.ReadConformantVaryingString(b, p, e)
+	if err != nil {
+		return
+	}
+	ah, err := ndr.ReadUniDimensionalConformantArrayHeader(b, p, e)
+	if err != nil {
+		return
+	}
+	switch c.Type {
+	case ClaimTypeIDInt64:
+		if ah.MaxCount != int(c.TypeInt64.ValueCount) {
+			return errors.New("error with size of CLAIM_ENTRY's value")
+		}
+		c.TypeInt64.Value = make([]int64, c.TypeInt64.ValueCount, c.TypeInt64.ValueCount)
+		for i := range c.TypeInt64.Value {
+			buf := bytes.NewReader((*b)[*p : *p+8])
+			err = binary.Read(buf, *e, &c.TypeInt64.Value[i])
+			if err != nil {
+				return
+			}
+			*p += 8 // progress position for a uint64
+		}
+	case ClaimTypeIDUInt64:
+		if ah.MaxCount != int(c.TypeUInt64.ValueCount) {
+			return errors.New("error with size of CLAIM_ENTRY's value")
+		}
+		c.TypeUInt64.Value = make([]uint64, c.TypeUInt64.ValueCount, c.TypeUInt64.ValueCount)
+		for i := range c.TypeUInt64.Value {
+			c.TypeUInt64.Value[i] = ndr.ReadUint64(b, p, e)
+		}
+	case ClaimTypeIDString:
+		if ah.MaxCount != int(c.TypeString.ValueCount) {
+			return errors.New("error with size of CLAIM_ENTRY's value")
+		}
+		c.TypeString.Value = make([]string, c.TypeString.ValueCount, c.TypeString.ValueCount)
+		*p += 4 * (int(c.TypeString.ValueCount)) // Move over pointers
+		for i := range c.TypeString.Value {
+			c.TypeString.Value[i], err = ndr.ReadConformantVaryingString(b, p, e)
+			if err != nil {
+				return
+			}
+		}
+	case ClaimsTypeIDBoolean:
+		if ah.MaxCount != int(c.TypeBool.ValueCount) {
+			return errors.New("error with size of CLAIM_ENTRY's value")
+		}
+		c.TypeBool.Value = make([]bool, c.TypeBool.ValueCount, c.TypeBool.ValueCount)
+		for i := range c.TypeBool.Value {
+			if ndr.ReadUint64(b, p, e) != 0 {
+				c.TypeBool.Value[i] = true
+			}
+		}
+	}
+	return
+}

+ 0 - 39
mstypes/claims_set_metadata.go

@@ -1,39 +0,0 @@
-package mstypes
-
-import (
-	"encoding/binary"
-
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
-)
-
-// ClaimsSetMetadata implements https://msdn.microsoft.com/en-us/library/hh554073.aspx
-type ClaimsSetMetadata struct {
-	ULClaimsSetSize             uint32
-	ClaimsSet                   []byte
-	USCompressionFormat         uint32 // Enum see constants below for options
-	ULUncompressedClaimsSetSize uint32
-	USReservedType              uint16
-	ULReservedFieldSize         uint32
-	ReservedField               []byte
-}
-
-// Compression format assigned numbers.
-const (
-	CompressionFormatNone       = 0
-	CompressionFormatLZNT1      = 2
-	CompressionFormatXPress     = 3
-	CompressionFormatXPressHuff = 4
-)
-
-// ReadClaimsSetMetadata reads a ClaimsSetMetadata from the bytes slice.
-func ReadClaimsSetMetadata(b *[]byte, p *int, e *binary.ByteOrder) ClaimsSetMetadata {
-	var c ClaimsSetMetadata
-	c.ULClaimsSetSize = ndr.ReadUint32(b, p, e)
-	c.ClaimsSet = ndr.ReadBytes(b, p, int(c.ULClaimsSetSize), e)
-	c.USCompressionFormat = ndr.ReadUint32(b, p, e)
-	c.ULUncompressedClaimsSetSize = ndr.ReadUint32(b, p, e)
-	c.USReservedType = ndr.ReadUint16(b, p, e)
-	c.ULReservedFieldSize = ndr.ReadUint32(b, p, e)
-	c.ReservedField = ndr.ReadBytes(b, p, int(c.ULReservedFieldSize), e)
-	return c
-}

+ 1 - 1
mstypes/filetime.go

@@ -5,7 +5,7 @@ import (
 	"encoding/binary"
 	"time"
 
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 /*

+ 1 - 1
mstypes/group_membership.go

@@ -3,7 +3,7 @@ package mstypes
 import (
 	"encoding/binary"
 
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // GroupMembership implements https://msdn.microsoft.com/en-us/library/cc237945.aspx

+ 1 - 1
mstypes/kerb_sid_and_attributes.go

@@ -3,7 +3,7 @@ package mstypes
 import (
 	"encoding/binary"
 
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // Attributes of a security group membership and can be combined by using the bitwise OR operation.

+ 1 - 1
mstypes/rpc_unicode_string.go

@@ -3,7 +3,7 @@ package mstypes
 import (
 	"encoding/binary"
 
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // RPCUnicodeString implements https://msdn.microsoft.com/en-us/library/cc230365.aspx

+ 1 - 1
mstypes/sid.go

@@ -5,7 +5,7 @@ import (
 	"encoding/hex"
 	"fmt"
 
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // RPCSID implements https://msdn.microsoft.com/en-us/library/cc230364.aspx

+ 1 - 1
mstypes/user_session_key.go

@@ -3,7 +3,7 @@ package mstypes
 import (
 	"encoding/binary"
 
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // CypherBlock implements https://msdn.microsoft.com/en-us/library/cc237040.aspx

+ 1 - 1
ndr/ndr.go

@@ -1,4 +1,4 @@
-// Package ndr is a partial implementation of NDR encoding: http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm
+// Package ndr is DEPRECATED and will be removed from next major revision of gokrb5. Please use gopkg.in/jcmturner/rpc.vX instead. This package is a partial implementation of NDR encoding: http://pubs.opengroup.org/onlinepubs/9629399/chap14.htm
 package ndr
 
 import (

+ 9 - 6
pac/client_claims.go

@@ -4,9 +4,11 @@ import (
 	"fmt"
 
 	"gopkg.in/jcmturner/gokrb5.v5/mstypes"
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
+// Claims reference: https://msdn.microsoft.com/en-us/library/hh553895.aspx
+
 // ClientClaimsInfo implements https://msdn.microsoft.com/en-us/library/hh536365.aspx
 type ClientClaimsInfo struct {
 	Claims mstypes.ClaimsSetMetadata
@@ -16,23 +18,24 @@ type ClientClaimsInfo struct {
 func (k *ClientClaimsInfo) Unmarshal(b []byte) error {
 	ch, _, p, err := ndr.ReadHeaders(&b)
 	if err != nil {
-		return fmt.Errorf("error parsing byte stream headers: %v", err)
+		return fmt.Errorf("error parsing byte stream headers of CLIENT_CLAIMS_INFO: %v", err)
 	}
 	e := &ch.Endianness
-
 	//The next 4 bytes are an RPC unique pointer referent. We just skip these
 	p += 4
 
-	k.Claims = mstypes.ReadClaimsSetMetadata(&b, &p, e)
+	k.Claims, err = mstypes.ReadClaimsSetMetadata(&b, &p, e)
+	if err != nil {
+		return err
+	}
 
 	//Check that there is only zero padding left
 	if len(b) >= p {
 		for _, v := range b[p:] {
 			if v != 0 {
-				return ndr.Malformed{EText: "Non-zero padding left over at end of data stream"}
+				return ndr.Malformed{EText: "non-zero padding left over at end of data stream"}
 			}
 		}
 	}
-
 	return nil
 }

+ 144 - 0
pac/client_claims_test.go

@@ -0,0 +1,144 @@
+package pac
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"gopkg.in/jcmturner/gokrb5.v5/mstypes"
+	"gopkg.in/jcmturner/gokrb5.v5/testdata"
+)
+
+const (
+	ClaimsEntryIDStr            = "ad://ext/sAMAccountName:88d5d9085ea5c0c0"
+	ClaimsEntryValueStr         = "testuser1"
+	ClaimsEntryIDInt64          = "ad://ext/msDS-SupportedE:88d5dea8f1af5f19"
+	ClaimsEntryValueInt64 int64 = 28
+	ClaimsEntryIDUInt64         = "ad://ext/objectClass:88d5de791e7b27e6"
+)
+
+func TestPAC_ClientClaimsInfoStr_Unmarshal(t *testing.T) {
+	t.Parallel()
+	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoStr"])
+	if err != nil {
+		t.Fatal("Could not decode test data hex string")
+	}
+	var k ClientClaimsInfo
+	err = k.Unmarshal(b)
+	if err != nil {
+		t.Fatalf("Error unmarshaling test data: %v", err)
+	}
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, uint16(3), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeString.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDStr, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []string{ClaimsEntryValueStr}, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeString.Value, "claims value not as expected")
+	assert.Equal(t, mstypes.CompressionFormatNone, k.Claims.CompressionFormat, "compression format not as expected")
+}
+
+func TestPAC_ClientClaimsMultiValueUint_Unmarshal(t *testing.T) {
+	t.Parallel()
+	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoMultiUint"])
+	if err != nil {
+		t.Fatal("Could not decode test data hex string")
+	}
+	var k ClientClaimsInfo
+	err = k.Unmarshal(b)
+	if err != nil {
+		t.Fatalf("Error unmarshaling test data: %v", err)
+	}
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, mstypes.ClaimTypeIDUInt64, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(4), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeUInt64.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDUInt64, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []uint64{655369, 65543, 65542, 65536}, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeUInt64.Value, "claims value not as expected")
+	assert.Equal(t, mstypes.CompressionFormatNone, k.Claims.CompressionFormat, "compression format not as expected")
+}
+
+func TestPAC_ClientClaimsInt_Unmarshal(t *testing.T) {
+	t.Parallel()
+	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoInt"])
+	if err != nil {
+		t.Fatal("Could not decode test data hex string")
+	}
+	var k ClientClaimsInfo
+	err = k.Unmarshal(b)
+	if err != nil {
+		t.Fatalf("Error unmarshaling test data: %v", err)
+	}
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, mstypes.ClaimTypeIDInt64, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeInt64.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDInt64, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []int64{ClaimsEntryValueInt64}, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeInt64.Value, "claims value not as expected")
+	assert.Equal(t, mstypes.CompressionFormatNone, k.Claims.CompressionFormat, "compression format not as expected")
+}
+
+func TestPAC_ClientClaimsMultiValueStr_Unmarshal(t *testing.T) {
+	t.Parallel()
+	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoMultiStr"])
+	if err != nil {
+		t.Fatal("Could not decode test data hex string")
+	}
+	var k ClientClaimsInfo
+	err = k.Unmarshal(b)
+	if err != nil {
+		t.Fatalf("Error unmarshaling test data: %v", err)
+	}
+	t.Logf("%+v\n", k)
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, mstypes.ClaimTypeIDString, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(4), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeString.ValueCount, "claims value count not as expected")
+	assert.Equal(t, "ad://ext/otherIpPhone:88d5de9f6b4af985", k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []string{"str1", "str2", "str3", "str4"}, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeString.Value, "claims value not as expected")
+	assert.Equal(t, mstypes.CompressionFormatNone, k.Claims.CompressionFormat, "compression format not as expected")
+}
+
+func TestPAC_ClientClaimsInfoMultiEntry_Unmarshal(t *testing.T) {
+	// Has an int and a str claim type
+	t.Parallel()
+	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfoMulti"])
+	if err != nil {
+		t.Fatal("Could not decode test data hex string")
+	}
+	var k ClientClaimsInfo
+	err = k.Unmarshal(b)
+	if err != nil {
+		t.Fatalf("Error unmarshaling test data: %v", err)
+	}
+	t.Logf("%+v\n", k)
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrayCount, "claims array count not as expected")
+	assert.Equal(t, mstypes.ClaimsSourceTypeAD, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsSourceType, "claims source type not as expected")
+	assert.Equal(t, uint32(2), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsCount, "claims count not as expected")
+	assert.Equal(t, uint16(1), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeInt64.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDInt64, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].ID, "claims entry ID not as expected")
+	assert.Equal(t, []int64{int64(28)}, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[0].TypeInt64.Value, "claims value not as expected")
+	assert.Equal(t, uint16(3), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[1].Type, "claims entry type not as expected")
+	assert.Equal(t, uint32(1), k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[1].TypeString.ValueCount, "claims value count not as expected")
+	assert.Equal(t, ClaimsEntryIDStr, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[1].ID, "claims entry ID not as expected")
+	assert.Equal(t, []string{ClaimsEntryValueStr}, k.Claims.ClaimsSet.ClaimsArrays[0].ClaimsEntries[1].TypeString.Value, "claims value not as expected")
+	assert.Equal(t, mstypes.CompressionFormatNone, k.Claims.CompressionFormat, "compression format not as expected")
+}
+
+func TestPAC_ClientClaimsInfo_Unmarshal_UnsupportedCompression(t *testing.T) {
+	t.Parallel()
+	b, err := hex.DecodeString(testdata.TestVectors["PAC_ClientClaimsInfo_XPRESS_HUFF"])
+	if err != nil {
+		t.Fatal("Could not decode test data hex string")
+	}
+	var k ClientClaimsInfo
+	err = k.Unmarshal(b)
+	if err != nil {
+		t.Fatalf("Error unmarshaling test data: %v", err)
+	}
+	assert.Equal(t, mstypes.CompressionFormatXPressHuff, k.Claims.CompressionFormat, "compression format not as expected")
+}

+ 3 - 8
pac/client_info.go

@@ -4,7 +4,7 @@ import (
 	"encoding/binary"
 
 	"gopkg.in/jcmturner/gokrb5.v5/mstypes"
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // ClientInfo implements https://msdn.microsoft.com/en-us/library/cc237951.aspx
@@ -25,18 +25,13 @@ func (k *ClientInfo) Unmarshal(b []byte) error {
 	if len(b[p:]) < int(k.NameLength) {
 		return ndr.Malformed{EText: "PAC ClientInfo length truncated"}
 	}
-	//Length divided by 2 as each run is 16bits = 2bytes
-	s := make([]rune, k.NameLength/2, k.NameLength/2)
-	for i := 0; i < len(s); i++ {
-		s[i] = rune(ndr.ReadUint16(&b, &p, &e))
-	}
-	k.Name = string(s)
+	k.Name = ndr.ReadUTF16String(int(k.NameLength), &b, &p, &e)
 
 	//Check that there is only zero padding left
 	if len(b) >= p {
 		for _, v := range b[p:] {
 			if v != 0 {
-				return ndr.Malformed{EText: "Non-zero padding left over at end of data stream"}
+				return ndr.Malformed{EText: "non-zero padding left over at end of data stream"}
 			}
 		}
 	}

+ 12 - 13
pac/credentials_info.go

@@ -2,13 +2,14 @@ package pac
 
 import (
 	"encoding/binary"
+	"errors"
 	"fmt"
 
 	"gopkg.in/jcmturner/gokrb5.v5/crypto"
 	"gopkg.in/jcmturner/gokrb5.v5/iana/keyusage"
 	"gopkg.in/jcmturner/gokrb5.v5/mstypes"
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
 	"gopkg.in/jcmturner/gokrb5.v5/types"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // https://msdn.microsoft.com/en-us/library/cc237931.aspx
@@ -23,20 +24,18 @@ type CredentialsInfo struct {
 
 // Unmarshal bytes into the CredentialsInfo struct
 func (c *CredentialsInfo) Unmarshal(b []byte, k types.EncryptionKey) error {
-	ch, _, p, err := ndr.ReadHeaders(&b)
-	if err != nil {
-		return fmt.Errorf("error parsing byte stream headers: %v", err)
-	}
-	e := &ch.Endianness
-
-	//The next 4 bytes are an RPC unique pointer referent. We just skip these
-	p += 4
+	//The CredentialsInfo structure is a simple structure that is not NDR-encoded.
+	var p int
+	var e binary.ByteOrder = binary.LittleEndian
 
-	c.Version = ndr.ReadUint32(&b, &p, e)
-	c.EType = ndr.ReadUint32(&b, &p, e)
-	c.PACCredentialDataEncrypted = ndr.ReadBytes(&b, &p, len(b)-p, e)
+	c.Version = ndr.ReadUint32(&b, &p, &e)
+	if c.Version != 0 {
+		return errors.New("credentials info version is not zero")
+	}
+	c.EType = ndr.ReadUint32(&b, &p, &e)
+	c.PACCredentialDataEncrypted = ndr.ReadBytes(&b, &p, len(b)-p, &e)
 
-	err = c.DecryptEncPart(k, e)
+	err := c.DecryptEncPart(k, &e)
 	if err != nil {
 		return fmt.Errorf("error decrypting PAC Credentials Data: %v", err)
 	}

+ 6 - 5
pac/device_claims.go

@@ -4,7 +4,7 @@ import (
 	"fmt"
 
 	"gopkg.in/jcmturner/gokrb5.v5/mstypes"
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // DeviceClaimsInfo implements https://msdn.microsoft.com/en-us/library/hh554226.aspx
@@ -16,14 +16,16 @@ type DeviceClaimsInfo struct {
 func (k *DeviceClaimsInfo) Unmarshal(b []byte) error {
 	ch, _, p, err := ndr.ReadHeaders(&b)
 	if err != nil {
-		return fmt.Errorf("error parsing byte stream headers: %v", err)
+		return fmt.Errorf("error parsing byte stream headers of DEVICE_CLAIMS_INFO: %v", err)
 	}
 	e := &ch.Endianness
-
 	//The next 4 bytes are an RPC unique pointer referent. We just skip these
 	p += 4
 
-	k.Claims = mstypes.ReadClaimsSetMetadata(&b, &p, e)
+	k.Claims, err = mstypes.ReadClaimsSetMetadata(&b, &p, e)
+	if err != nil {
+		return err
+	}
 
 	//Check that there is only zero padding left
 	if len(b) >= p {
@@ -33,6 +35,5 @@ func (k *DeviceClaimsInfo) Unmarshal(b []byte) error {
 			}
 		}
 	}
-
 	return nil
 }

+ 5 - 4
pac/device_info.go

@@ -4,7 +4,7 @@ import (
 	"fmt"
 
 	"gopkg.in/jcmturner/gokrb5.v5/mstypes"
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // DeviceInfo implements https://msdn.microsoft.com/en-us/library/hh536402.aspx
@@ -47,10 +47,11 @@ func (k *DeviceInfo) Unmarshal(b []byte) error {
 	}
 
 	k.SIDCount = ndr.ReadUint32(&b, &p, e)
+	var ah ndr.ConformantArrayHeader
 	if k.SIDCount > 0 {
-		ac := ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
-		if ac != int(k.SIDCount) {
-			return fmt.Errorf("error with size of ExtraSIDs list. expected: %d, Actual: %d", k.SIDCount, ac)
+		ah, err = ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
+		if ah.MaxCount != int(k.SIDCount) {
+			return fmt.Errorf("error with size of ExtraSIDs list. expected: %d, Actual: %d", k.SIDCount, ah.MaxCount)
 		}
 		es := make([]mstypes.KerbSidAndAttributes, k.SIDCount, k.SIDCount)
 		attr := make([]uint32, k.SIDCount, k.SIDCount)

+ 22 - 12
pac/kerb_validation_info.go

@@ -6,7 +6,7 @@ import (
 	"fmt"
 
 	"gopkg.in/jcmturner/gokrb5.v5/mstypes"
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // KERB_VALIDATION_INFO flags.
@@ -168,11 +168,15 @@ func (k *KerbValidationInfo) Unmarshal(b []byte) (err error) {
 	if err = k.HomeDirectoryDrive.UnmarshalString(&b, &p, e); err != nil {
 		return
 	}
-
+	var ah ndr.ConformantArrayHeader
 	if k.GroupCount > 0 {
-		ac := ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
-		if ac != int(k.GroupCount) {
-			return errors.New("error with size of group list")
+		ah, err = ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
+		if err != nil {
+			return
+		}
+		if ah.MaxCount != int(k.GroupCount) {
+			err = errors.New("error with size of group list")
+			return
 		}
 		g := make([]mstypes.GroupMembership, k.GroupCount, k.GroupCount)
 		for i := range g {
@@ -196,9 +200,12 @@ func (k *KerbValidationInfo) Unmarshal(b []byte) (err error) {
 	}
 
 	if k.SIDCount > 0 {
-		ac := ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
-		if ac != int(k.SIDCount) {
-			return fmt.Errorf("error with size of ExtraSIDs list. Expected: %d, Actual: %d", k.SIDCount, ac)
+		ah, err = ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
+		if err != nil {
+			return
+		}
+		if ah.MaxCount != int(k.SIDCount) {
+			return fmt.Errorf("error with size of ExtraSIDs list. Expected: %d, Actual: %d", k.SIDCount, ah.MaxCount)
 		}
 		es := make([]mstypes.KerbSidAndAttributes, k.SIDCount, k.SIDCount)
 		attr := make([]uint32, k.SIDCount, k.SIDCount)
@@ -227,11 +234,14 @@ func (k *KerbValidationInfo) Unmarshal(b []byte) (err error) {
 	}
 
 	if k.ResourceGroupCount > 0 {
-		ac := ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
-		if ac != int(k.ResourceGroupCount) {
-			return fmt.Errorf("error with size of ResourceGroup list. Expected: %d, Actual: %d", k.ResourceGroupCount, ac)
+		ah, err = ndr.ReadUniDimensionalConformantArrayHeader(&b, &p, e)
+		if err != nil {
+			return
+		}
+		if ah.MaxCount != int(k.ResourceGroupCount) {
+			return fmt.Errorf("error with size of ResourceGroup list. Expected: %d, Actual: %d", k.ResourceGroupCount, ah.MaxCount)
 		}
-		g := make([]mstypes.GroupMembership, ac, ac)
+		g := make([]mstypes.GroupMembership, k.ResourceGroupCount, k.ResourceGroupCount)
 		for i := range g {
 			g[i] = mstypes.ReadGroupMembership(&b, &p, e)
 		}

+ 1 - 1
pac/pac_info_buffer.go

@@ -3,7 +3,7 @@ package pac
 import (
 	"encoding/binary"
 
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 const (

+ 16 - 12
pac/pac_type.go

@@ -7,8 +7,8 @@ import (
 
 	"gopkg.in/jcmturner/gokrb5.v5/crypto"
 	"gopkg.in/jcmturner/gokrb5.v5/iana/keyusage"
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
 	"gopkg.in/jcmturner/gokrb5.v5/types"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // PACType implements: https://msdn.microsoft.com/en-us/library/cc237950.aspx
@@ -67,16 +67,20 @@ func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey) error {
 			}
 			pac.KerbValidationInfo = &k
 		case ulTypeCredentials:
-			if pac.CredentialsInfo != nil {
-				//Must ignore subsequent buffers of this type
-				continue
-			}
-			var k CredentialsInfo
-			err := k.Unmarshal(p, key)
-			if err != nil {
-				return fmt.Errorf("error processing CredentialsInfo: %v", err)
-			}
-			pac.CredentialsInfo = &k
+			// Currently PAC parsing is only useful on the service side in gokrb5
+			// The CredentialsInfo are only useful when gokrb5 has implemented RFC4556 and only applied on the client side.
+			// Skipping CredentialsInfo - will be revisited under RFC4556 implementation.
+			continue
+			//if pac.CredentialsInfo != nil {
+			//	//Must ignore subsequent buffers of this type
+			//	continue
+			//}
+			//var k CredentialsInfo
+			//err := k.Unmarshal(p, key) // The encryption key used is the AS reply key only available to the client.
+			//if err != nil {
+			//	return fmt.Errorf("error processing CredentialsInfo: %v", err)
+			//}
+			//pac.CredentialsInfo = &k
 		case ulTypePACServerSignatureData:
 			if pac.ServerChecksum != nil {
 				//Must ignore subsequent buffers of this type
@@ -135,7 +139,7 @@ func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey) error {
 			}
 			pac.UPNDNSInfo = &k
 		case ulTypePACClientClaimsInfo:
-			if pac.ClientClaimsInfo != nil {
+			if pac.ClientClaimsInfo != nil || len(p) < 1 {
 				//Must ignore subsequent buffers of this type
 				continue
 			}

+ 1 - 1
pac/s4u_delegation_info.go

@@ -4,7 +4,7 @@ import (
 	"fmt"
 
 	"gopkg.in/jcmturner/gokrb5.v5/mstypes"
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // S4UDelegationInfo implements https://msdn.microsoft.com/en-us/library/cc237944.aspx

+ 2 - 2
pac/signature_data.go

@@ -4,7 +4,7 @@ import (
 	"encoding/binary"
 
 	"gopkg.in/jcmturner/gokrb5.v5/iana/chksumtype"
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 /*
@@ -60,7 +60,7 @@ func (k *SignatureData) Unmarshal(b []byte) ([]byte, error) {
 	//Check that there is only zero padding left
 	for _, v := range b[p:] {
 		if v != 0 {
-			return []byte{}, ndr.Malformed{EText: "Non-zero padding left over at end of data stream"}
+			return []byte{}, ndr.Malformed{EText: "non-zero padding left over at end of data stream"}
 		}
 	}
 

+ 2 - 2
pac/upn_dns_info.go

@@ -4,7 +4,7 @@ import (
 	"encoding/binary"
 	"sort"
 
-	"gopkg.in/jcmturner/gokrb5.v5/ndr"
+	"gopkg.in/jcmturner/rpc.v0/ndr"
 )
 
 // UPNDNSInfo implements https://msdn.microsoft.com/en-us/library/dd240468.aspx
@@ -58,7 +58,7 @@ func (k *UPNDNSInfo) Unmarshal(b []byte) error {
 	//Check that there is only zero padding left
 	for _, v := range b[l[2]:] {
 		if v != 0 {
-			return ndr.Malformed{EText: "Non-zero padding left over at end of data stream."}
+			return ndr.Malformed{EText: "non-zero padding left over at end of data stream."}
 		}
 	}
 

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
testdata/test_vectors.go


+ 30 - 0
testenv/ActiveDirectory.md

@@ -0,0 +1,30 @@
+# Active Directory Test Environment Setup Notes
+
+
+## Claims
+* Needs Windows 2012
+### Enable Claims
+* Administrative Tools > Group Policy Management
+  * Forest > Domains > DOMAIN.COM > Default Domain Policy (right click, Edit)
+  * Compute Configuration > Policies > Administrative Templates > System > KDC
+    * Edit "KDC Support for claims"
+    * Set to "Enabled" with the option "Always provide claims"
+    
+### Configure Claims Values
+* Administrative Tools > Active Directory Administrative Center
+  * Dynamic Access Control > Claim Types > New
+
+| Display name | Attribute | Type |
+| -------------|-----------|------|
+| username | sAMAccountName | string |
+| msTSAllowLogon | msTSAllowLogon | boolean |
+| sAMAccountType | sAMAccountType | Integer |
+| objectClass | objectClass | multi-valued unsigned integer |
+| ou | ou | multi-valued string |
+| postalAddress | postalAddress | multi-valued string |
+
+### Inspect Values
+```
+Get-ADUser -Filter 'Name -like "*test*1*" -properties *
+```
+    

BIN
testenv/testuser2-USER.GOKRB5.testtab


Некоторые файлы не были показаны из-за большого количества измененных файлов