Jonathan Turner 9 лет назад
Родитель
Сommit
bc51b2e5b7
6 измененных файлов с 114 добавлено и 78 удалено
  1. 36 8
      crypto/EncryptionEngine.go
  2. 11 8
      crypto/aes-cts-hmac-sha1-96.go
  3. 19 15
      debug.go
  4. 23 37
      messages/KDCRep.go
  5. 10 10
      messages/KDCReq.go
  6. 15 0
      types/PAData.go

+ 36 - 8
crypto/EncryptionEngine.go

@@ -3,14 +3,16 @@ package crypto
 import (
 	"bytes"
 	"crypto/hmac"
+	"crypto/rand"
 	"encoding/binary"
 	"encoding/hex"
 	"errors"
 	"fmt"
+	"github.com/jcmturner/gokrb5/iana/chksumtype"
+	"github.com/jcmturner/gokrb5/iana/etype"
 	"github.com/jcmturner/gokrb5/iana/patype"
 	"github.com/jcmturner/gokrb5/types"
 	"hash"
-	"crypto/rand"
 )
 
 type EType interface {
@@ -35,10 +37,10 @@ type EType interface {
 
 func GetEtype(id int) (EType, error) {
 	switch id {
-	case 17:
+	case etype.AES128_CTS_HMAC_SHA1_96:
 		var et Aes128CtsHmacSha96
 		return et, nil
-	case 18:
+	case etype.AES256_CTS_HMAC_SHA1_96:
 		var et Aes256CtsHmacSha96
 		return et, nil
 	default:
@@ -46,6 +48,19 @@ func GetEtype(id int) (EType, error) {
 	}
 }
 
+func GetChksumEtype(id int) (EType, error) {
+	switch id {
+	case chksumtype.HMAC_SHA1_96_AES128:
+		var et Aes128CtsHmacSha96
+		return et, nil
+	case chksumtype.HMAC_SHA1_96_AES256:
+		var et Aes256CtsHmacSha96
+		return et, nil
+	default:
+		return nil, fmt.Errorf("Unknown or unsupported checksum type: %d", id)
+	}
+}
+
 // RFC3961: DR(Key, Constant) = k-truncate(E(Key, Constant, initial-cipher-state))
 // key - base key or protocol key. Likely to be a key from a keytab file
 // usage - a constant
@@ -143,7 +158,7 @@ func DecryptEncPart(key []byte, pe types.EncryptedData, etype EType, usage uint3
 	}
 	//Verify checksum
 	if !etype.VerifyIntegrity(key, pe.Cipher, b, usage) {
-			return nil, errors.New("Error decrypting encrypted part: integrity verification failed")
+		return nil, errors.New("Error decrypting encrypted part: integrity verification failed")
 	}
 	//Remove the confounder bytes
 	b = b[etype.GetConfounderByteSize():]
@@ -214,7 +229,7 @@ func GetKeyFromPassword(passwd string, cn types.PrincipalName, realm string, ety
 		return key, etype, fmt.Errorf("Error deriving key from string: %+v", err)
 	}
 	key = types.EncryptionKey{
-		KeyType: etypeId,
+		KeyType:  etypeId,
 		KeyValue: k,
 	}
 	return key, etype, nil
@@ -226,7 +241,9 @@ func getHash(pt, key []byte, usage []byte, etype EType) ([]byte, error) {
 		return nil, fmt.Errorf("Unable to derive key for checksum: %v", err)
 	}
 	mac := hmac.New(etype.GetHash, k)
-	mac.Write(pt)
+	p := make([]byte, len(pt))
+	copy(p, pt)
+	mac.Write(p)
 	return mac.Sum(nil)[:etype.GetHMACBitLength()/8], nil
 }
 
@@ -251,6 +268,17 @@ func VerifyIntegrity(key, ct, pt []byte, usage uint32, etype EType) bool {
 	return hmac.Equal(h, expectedMAC)
 }
 
+func VerifyChecksum(key, chksum, msg []byte, usage uint32, etype EType) bool {
+	//The ciphertext output is the concatenation of the output of the basic
+	//encryption function E and a (possibly truncated) HMAC using the
+	//specified hash function H, both applied to the plaintext with a
+	//random confounder prefix and sufficient padding to bring it to a
+	//multiple of the message block size.  When the HMAC is computed, the
+	//key is used in the protocol key form.
+	expectedMAC, _ := GetChecksumHash(msg, key, usage, etype)
+	return hmac.Equal(chksum, expectedMAC)
+}
+
 /*
 Key Usage Numbers
 RFC 3961: The "well-known constant" used for the DK function is the key usage number, expressed as four octets in big-endian order, followed by one octet indicated below.
@@ -308,9 +336,9 @@ func GetEncryptedData(pt []byte, key types.EncryptionKey, usage int, kvno int) (
 	ih, err := GetIntegrityHash(pt, key.KeyValue, uint32(usage), etype)
 	b = append(b, ih...)
 	ed = types.EncryptedData{
-		EType: key.KeyType,
+		EType:  key.KeyType,
 		Cipher: b,
-		KVNO: kvno,
+		KVNO:   kvno,
 	}
 	return ed, nil
 }

+ 11 - 8
crypto/aes-cts-hmac-sha1-96.go

@@ -81,6 +81,9 @@ func AESCTSEncrypt(key, iv, message []byte, e EType) ([]byte, []byte, error) {
 	}
 	mode := cipher.NewCBCEncrypter(block, iv)
 
+	m := make([]byte, len(message))
+	copy(m, message)
+
 	//Ref: https://tools.ietf.org/html/rfc3962 section 5
 	/*For consistency, ciphertext stealing is always used for the last two
 	blocks of the data to be encrypted, as in [RC5].  If the data length
@@ -90,18 +93,18 @@ func AESCTSEncrypt(key, iv, message []byte, e EType) ([]byte, []byte, error) {
 	subsequent encryption is the next-to-last block of the encryption
 	output; this is the encrypted form of the last plaintext block.*/
 	if l <= aes.BlockSize {
-		message, _ = zeroPad(message, aes.BlockSize)
-		mode.CryptBlocks(message, message)
-		return message, message, nil
+		m, _ = zeroPad(m, aes.BlockSize)
+		mode.CryptBlocks(m, m)
+		return m, m, nil
 	}
 	if l%aes.BlockSize == 0 {
-		mode.CryptBlocks(message, message)
-		iv = message[len(message)-aes.BlockSize:]
-		rb, _ := swapLastTwoBlocks(message, aes.BlockSize)
+		mode.CryptBlocks(m, m)
+		iv = m[len(m)-aes.BlockSize:]
+		rb, _ := swapLastTwoBlocks(m, aes.BlockSize)
 		return iv, rb, nil
 	}
-	message, _ = zeroPad(message, aes.BlockSize)
-	rb, pb, lb, err := tailBlocks(message, aes.BlockSize)
+	m, _ = zeroPad(m, aes.BlockSize)
+	rb, pb, lb, err := tailBlocks(m, aes.BlockSize)
 	var ct []byte
 	if rb != nil {
 		// Encrpt all but the lats 2 blocks and update the rolling iv

+ 19 - 15
debug.go

@@ -11,8 +11,8 @@ import (
 	"github.com/jcmturner/gokrb5/iana/keyusage"
 	"github.com/jcmturner/gokrb5/keytab"
 	"github.com/jcmturner/gokrb5/messages"
-	"github.com/jcmturner/gokrb5/types"
 	"github.com/jcmturner/gokrb5/testdata"
+	"github.com/jcmturner/gokrb5/types"
 	"os"
 	"time"
 )
@@ -141,33 +141,37 @@ func TestTGSReq() {
 
 	err = cl.ASExchange()
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error on AS_REQ: %v", err)
+		fmt.Fprintf(os.Stderr, "Error on AS_REQ: %v\n", err)
 	}
 	fmt.Fprintf(os.Stderr, "Client: %+v\n", cl)
 
-/*	var a messages.TGSReq
-	b, err = hex.DecodeString(testdata.TEST_TGS_REQ)
+	tgs, err := messages.NewTGSReq("testuser1", c, cl.Session.TGT, cl.Session.SessionKey, "HTTP/host.test.gokrb5")
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Test vector read error: %v\n", err)
+		fmt.Fprintf(os.Stderr, "Error on New TGS_REQ: %v\n", err)
 	}
-	err = a.Unmarshal(b)
+	fmt.Fprintf(os.Stderr, "TGS_REQ gen: %+v\n", tgs)
+	var apreq messages.APReq
+	apreq.Unmarshal(tgs.PAData[0].PADataValue)
+	fmt.Fprintf(os.Stderr, "cb authenticator: %v\n", apreq.Authenticator.Cipher)
+	etype, _ := crypto.GetEtype(cl.Session.SessionKey.KeyType)
+	b, err = crypto.DecryptEncPart(cl.Session.SessionKey.KeyValue, apreq.Authenticator, etype, uint32(keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR))
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Unmarshal error: %v\n", err)
+		fmt.Fprintf(os.Stderr, "Error decrypting authenticator: %v\n", err)
 	}
-	fmt.Fprintf(os.Stderr, "TGS_REQ: %+v\n", a)*/
-
-	tgs, err := messages.NewTGSReq("testuser1", c, cl.Session.TGT, cl.Session.SessionKey, "HTTP/host.test.gokrb5")
+	var a types.Authenticator
+	err = a.Unmarshal(b)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error on New TGS_REQ: %v", err)
+		fmt.Fprintf(os.Stderr, "Error unmarshal authenticator: %v\n", err)
 	}
-	fmt.Fprintf(os.Stderr, "TGS_REQ gen: %+v\n", tgs)
+	fmt.Fprintf(os.Stderr, "authenticator: %+v\n", a)
+
 	b, err = tgs.Marshal()
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error marshalling TGS_REQ: %v", err)
+		fmt.Fprintf(os.Stderr, "Error marshalling TGS_REQ: %v\n", err)
 	}
 	_, err = cl.SendToKDC(b)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error sending TGS_REQ to KDC: %v", err)
+		fmt.Fprintf(os.Stderr, "Error sending TGS_REQ to KDC: %v\n", err)
 	}
 
-}
+}

+ 23 - 37
messages/KDCRep.go

@@ -13,9 +13,9 @@ import (
 	"github.com/jcmturner/gokrb5/iana/asnAppTag"
 	"github.com/jcmturner/gokrb5/iana/keyusage"
 	"github.com/jcmturner/gokrb5/iana/msgtype"
+	"github.com/jcmturner/gokrb5/iana/patype"
 	"github.com/jcmturner/gokrb5/keytab"
 	"github.com/jcmturner/gokrb5/types"
-	"sort"
 	"time"
 )
 
@@ -169,8 +169,6 @@ func (k *ASRep) IsValid(cfg *config.Config, asReq ASReq) (bool, error) {
 	if k.CName.NameType != asReq.ReqBody.CName.NameType {
 		return false, fmt.Errorf("CName in response does not match what was requested. Requested: %+v; Reply: %+v", asReq.ReqBody.CName, k.CName)
 	}
-	sort.Strings(k.CName.NameString)
-	sort.Strings(asReq.ReqBody.CName.NameString)
 	for i := range k.CName.NameString {
 		if k.CName.NameString[i] != asReq.ReqBody.CName.NameString[i] {
 			return false, fmt.Errorf("CName in response does not match what was requested. Requested: %+v; Reply: %+v", asReq.ReqBody.CName, k.CName)
@@ -185,8 +183,6 @@ func (k *ASRep) IsValid(cfg *config.Config, asReq ASReq) (bool, error) {
 	if k.DecryptedEncPart.SName.NameType != asReq.ReqBody.SName.NameType {
 		return false, fmt.Errorf("SName in response does not match what was requested. Requested: %v; Reply: %v", asReq.ReqBody.SName, k.DecryptedEncPart.SName)
 	}
-	sort.Strings(k.DecryptedEncPart.SName.NameString)
-	sort.Strings(asReq.ReqBody.SName.NameString)
 	for i := range k.CName.NameString {
 		if k.DecryptedEncPart.SName.NameString[i] != asReq.ReqBody.SName.NameString[i] {
 			return false, fmt.Errorf("SName in response does not match what was requested. Requested: %+v; Reply: %+v", asReq.ReqBody.SName, k.DecryptedEncPart.SName)
@@ -201,6 +197,28 @@ func (k *ASRep) IsValid(cfg *config.Config, asReq ASReq) (bool, error) {
 	if time.Since(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew || time.Until(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew {
 		return false, fmt.Errorf("Clock skew with KDC too large. Greater than %v seconds", cfg.LibDefaults.Clockskew.Seconds())
 	}
+	if asReq.PAData.Contains(patype.PA_REQ_ENC_PA_REP) {
+		if len(k.DecryptedEncPart.EncPAData) < 2 || !k.DecryptedEncPart.EncPAData.Contains(patype.PA_FX_FAST) {
+			return false, errors.New("KDC did not respond appropriately to FAST negotiation")
+		}
+		for _, pa := range k.DecryptedEncPart.EncPAData {
+			if pa.PADataType == patype.PA_REQ_ENC_PA_REP {
+				var pafast types.PAReqEncPARep
+				err := pafast.Unmarshal(pa.PADataValue)
+				if err != nil {
+					return false, fmt.Errorf("KDC FAST negotiation response error, could not unmarshal PA_REQ_ENC_PA_REP: %v", err)
+				}
+				etype, err := crypto.GetChksumEtype(pafast.ChksumType)
+				if err != nil {
+					return false, fmt.Errorf("KDC FAST negotiation response error, %v", err)
+				}
+				ab, _ := asReq.Marshal()
+				if !crypto.VerifyChecksum(k.DecryptedEncPart.Key.KeyValue, pafast.Chksum, ab, keyusage.KEY_USAGE_AS_REQ, etype) {
+					return false, errors.New("KDC FAST negotiation response checksum invalid")
+				}
+			}
+		}
+	}
 	return true, nil
 }
 
@@ -225,35 +243,3 @@ func (k *TGSRep) DecryptEncPart(kt keytab.Keytab) error {
 	k.DecryptedEncPart = denc
 	return nil
 }
-
-// TODO put back after type tests complete to help me decide what to do with KDCRep vs ASRep and TGSRep
-//func validateKDCRep(k *KDCRep, asReq KDCReq, kt keytab.Keytab) (bool, error) {
-//	//Ref RFC 4120 Section 3.1.5
-//	//TODO change the following to a contains check or slice compare
-//	if k.CName.NameType != asReq.ReqBody.CName.NameType || k.CName.NameString[0] != asReq.ReqBody.CName.NameString[0] {
-//		return false, fmt.Errorf("CName in response does not match what was requested. Requested: %v; Reply: %v", asReq.ReqBody.CName, k.CName)
-//	}
-//	if k.CRealm != asReq.ReqBody.Realm {
-//		return false, fmt.Errorf("CRealm in response does not match what was requested. Requested: %s; Reply: %s", asReq.ReqBody.Realm, k.CRealm)
-//	}
-//	if k.DecryptedEncPart.Key.KeyType == 0 {
-//		err := k.DecryptEncPart(kt)
-//		if err != nil {
-//			return false, fmt.Errorf("Could not decrypt encrypted part of response: %v", err)
-//		}
-//	}
-//	if k.DecryptedEncPart.Nonce != asReq.ReqBody.Nonce {
-//		return false, errors.New("Possible replay attack, nonce in request does not match that in response")
-//	}
-//	//TODO change the following to a contains check or slice compare
-//	if k.DecryptedEncPart.SName.NameType != asReq.ReqBody.SName.NameType || k.DecryptedEncPart.SName.NameString[0] != asReq.ReqBody.SName.NameString[0] {
-//		return false, fmt.Errorf("SName in response does not match what was requested. Requested: %v; Reply: %v", asReq.ReqBody.SName, k.DecryptedEncPart.SName)
-//	}
-//	if k.DecryptedEncPart.SRealm != asReq.ReqBody.Realm {
-//		return false, fmt.Errorf("SRealm in response does not match what was requested. Requested: %s; Reply: %s", asReq.ReqBody.Realm, k.DecryptedEncPart.SRealm)
-//	}
-//	if len(asReq.ReqBody.Addresses) > 0 {
-//		//TODO compare if address list is the same
-//	}
-//	return true, nil
-//}

+ 10 - 10
messages/KDCReq.go

@@ -8,17 +8,17 @@ import (
 	"github.com/jcmturner/asn1"
 	"github.com/jcmturner/gokrb5/asn1tools"
 	"github.com/jcmturner/gokrb5/config"
+	"github.com/jcmturner/gokrb5/crypto"
 	"github.com/jcmturner/gokrb5/iana"
 	"github.com/jcmturner/gokrb5/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/iana/keyusage"
 	"github.com/jcmturner/gokrb5/iana/msgtype"
 	"github.com/jcmturner/gokrb5/iana/nametype"
 	"github.com/jcmturner/gokrb5/iana/patype"
 	"github.com/jcmturner/gokrb5/types"
 	"math/rand"
-	"time"
 	"strings"
-	"github.com/jcmturner/gokrb5/iana/keyusage"
-	"github.com/jcmturner/gokrb5/crypto"
+	"time"
 )
 
 type marshalKDCReq struct {
@@ -29,10 +29,10 @@ type marshalKDCReq struct {
 }
 
 type KDCReq struct {
-	PVNO    int            `asn1:"explicit,tag:1"`
-	MsgType int            `asn1:"explicit,tag:2"`
-	PAData  []types.PAData `asn1:"explicit,optional,tag:3"`
-	ReqBody KDCReqBody     `asn1:"explicit,tag:4"`
+	PVNO    int                  `asn1:"explicit,tag:1"`
+	MsgType int                  `asn1:"explicit,tag:2"`
+	PAData  types.PADataSequence `asn1:"explicit,optional,tag:3"`
+	ReqBody KDCReqBody           `asn1:"explicit,tag:4"`
 }
 
 type ASReq KDCReq
@@ -157,9 +157,9 @@ func NewTGSReq(username string, c *config.Config, TGT types.Ticket, sessionKey t
 		return a, fmt.Errorf("Error getting etype to encrypt authenticator: %v", err)
 	}
 	cb, err := crypto.GetChecksumHash(b, sessionKey.KeyValue, keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM, etype)
-	auth.Cksum = types.Checksum {
+	auth.Cksum = types.Checksum{
 		CksumType: etype.GetHashID(),
-		Checksum: cb,
+		Checksum:  cb,
 	}
 	apReq, err := NewAPReq(TGT, sessionKey, auth)
 	apb, err := apReq.Marshal()
@@ -168,7 +168,7 @@ func NewTGSReq(username string, c *config.Config, TGT types.Ticket, sessionKey t
 	}
 	pas := types.PADataSequence{
 		types.PAData{
-			PADataType: patype.PA_TGS_REQ,
+			PADataType:  patype.PA_TGS_REQ,
 			PADataValue: apb,
 		},
 	}

+ 15 - 0
types/PAData.go

@@ -15,6 +15,7 @@ type PAData struct {
 }
 
 type PADataSequence []PAData
+
 type MethodData []PAData
 
 type PAEncTimestamp EncryptedData
@@ -24,6 +25,15 @@ type PAEncTSEnc struct {
 	PAUSec      int       `asn1:"explicit,optional,tag:1"`
 }
 
+func (pas *PADataSequence) Contains(patype int) bool {
+	for _, pa := range *pas {
+		if pa.PADataType == patype {
+			return true
+		}
+	}
+	return false
+}
+
 func GetPAEncTSEncAsnMarshalled() ([]byte, error) {
 	t := time.Now()
 	p := PAEncTSEnc{
@@ -67,6 +77,11 @@ func (pa *PADataSequence) Unmarshal(b []byte) error {
 	return err
 }
 
+func (pa *PAReqEncPARep) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, pa)
+	return err
+}
+
 func (pa *PAEncTimestamp) Unmarshal(b []byte) error {
 	_, err := asn1.Unmarshal(b, pa)
 	return err