ソースを参照

testing http client

Jonathan Turner 9 年 前
コミット
fd0b22186f

+ 9 - 2
GSSAPI/NegotiationToken.go

@@ -4,6 +4,8 @@ import (
 	"errors"
 	"fmt"
 	"github.com/jcmturner/asn1"
+	"github.com/jcmturner/gokrb5/config"
+	"github.com/jcmturner/gokrb5/types"
 )
 
 /*
@@ -124,8 +126,13 @@ func (n *NegTokenResp) Marshal() ([]byte, error) {
 	return nb, nil
 }
 
-func NewNegTokenInitKrb5() NegTokenInit {
+func NewNegTokenInitKrb5(c config.Config, cname types.PrincipalName, tkt types.Ticket, sessionKey types.EncryptionKey) (NegTokenInit, error) {
+	mt, err := NewKRB5APREQMechToken(c, cname, tkt, sessionKey)
+	if err != nil {
+		return NegTokenInit{}, fmt.Errorf("Error getting MechToken; %v", err)
+	}
 	return NegTokenInit{
 		MechTypes: []asn1.ObjectIdentifier{MechTypeOID_Krb5},
-	}
+		MechToken: mt,
+	}, nil
 }

+ 10 - 6
GSSAPI/krb5Token.go

@@ -11,7 +11,6 @@ import (
 	"github.com/jcmturner/gokrb5/messages"
 	"github.com/jcmturner/gokrb5/types"
 	"math/rand"
-	"os"
 )
 
 const (
@@ -27,29 +26,34 @@ const (
 	GSS_C_INTEG_FLAG    = 32
 )
 
-func NewKRB5APREQMechToken(APReq messages.APReq) ([]byte, error) {
+func NewKRB5APREQMechToken(c config.Config, cname types.PrincipalName, tkt types.Ticket, sessionKey types.EncryptionKey) ([]byte, error) {
 	// Create the header
 	tb, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ)
 	b, _ := asn1.Marshal(MechTypeOID_Krb5)
 	b = append(b, tb...)
 	// Add the token
-	tb, err := APReq.Marshal()
+	APReq, err := messages.NewAPReq(
+		tkt,
+		sessionKey,
+		newAuthenticator(c, cname),
+	)
+	tb, err = APReq.Marshal()
 	if err != nil {
 		return []byte{}, fmt.Errorf("Could not marshal AP_REQ: %v", err)
 	}
 	b = append(b, tb...)
-	fmt.Fprintf(os.Stderr, "len %v\n", len(b))
 	return asn1tools.AddASNAppTag(b, 0), nil
 }
 
-func newAuthenticator(c config.Config, username string) {
+func newAuthenticator(c config.Config, username types.PrincipalName) types.Authenticator {
 	//RFC 4121 Section 4.1.1
 	auth := types.NewAuthenticator(c.LibDefaults.Default_realm, username)
 	auth.Cksum = types.Checksum{
 		CksumType: chksumtype.GSSAPI,
 		Checksum:  newAuthenticatorChksum([]int{GSS_C_INTEG_FLAG, GSS_C_CONF_FLAG}),
 	}
-	auth.SeqNumber = rand.Int63()
+	auth.SeqNumber = int(rand.Int63())
+	return auth
 }
 
 func newAuthenticatorChksum(flags []int) []byte {

+ 0 - 5
GSSAPI/krb5Token_test.go

@@ -2,8 +2,6 @@ package GSSAPI
 
 import (
 	"encoding/hex"
-	"github.com/jcmturner/gokrb5/asn1tools"
-	"github.com/jcmturner/gokrb5/messages"
 	"github.com/jcmturner/gokrb5/testdata"
 	"github.com/jcmturner/gokrb5/types"
 	"testing"
@@ -38,7 +36,4 @@ func TestKrb5Token_NewAPREQ(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Unmarshal error of %s: %v\n", "encode_krb5_keyblock", err)
 	}
-	APReq, err := messages.NewAPReq(tkt, k, a)
-	mb, err := NewKRB5APREQMechToken(APReq)
-	t.Logf("Mechtoken: %v, %v, %v", len(mb), asn1tools.GetLengthFromASN(mb), hex.EncodeToString(mb))
 }

+ 0 - 5
client/APExchange.go

@@ -1,5 +0,0 @@
-package client
-
-func (cl *Client) APExchange() {
-
-}

+ 1 - 1
client/ASExchange.go

@@ -21,7 +21,7 @@ func (cl *Client) ASExchange() error {
 	if !cl.IsConfigured() {
 		return errors.New("Client is not configured correctly.")
 	}
-	a := messages.NewASReq(cl.Config, cl.Credentials.Username)
+	a := messages.NewASReq(cl.Config, cl.Credentials.CName)
 	b, err := a.Marshal()
 	if err != nil {
 		return fmt.Errorf("Error marshalling AS_REQ: %v", err)

+ 8 - 7
client/TGSExchange.go

@@ -16,7 +16,7 @@ func (cl *Client) TGSExchange(spn types.PrincipalName, tkt types.Ticket, session
 	if cl.Session == nil {
 		return tgsReq, tgsRep, errors.New("Error client does not have a session. Client needs to login first")
 	}
-	tgsReq, err = messages.NewTGSReq(cl.Credentials.Username, cl.Config, tkt, sessionKey, spn, renewal)
+	tgsReq, err = messages.NewTGSReq(cl.Credentials.CName, cl.Config, tkt, sessionKey, spn, renewal)
 	if err != nil {
 		return tgsReq, tgsRep, fmt.Errorf("Error generating New TGS_REQ: %v", err)
 	}
@@ -45,17 +45,18 @@ func (cl *Client) TGSExchange(spn types.PrincipalName, tkt types.Ticket, session
 // Make a request to get a service ticket for the SPN specified
 // SPN format: <SERVICE>/<FQDN> Eg. HTTP/www.example.com
 // The ticket will be added to the client's ticket cache
-func (cl *Client) GetServiceTicket(spn string) (types.Ticket, error) {
+func (cl *Client) GetServiceTicket(spn string) (types.Ticket, types.EncryptionKey, error) {
 	var tkt types.Ticket
-	if tkt, ok := cl.GetCachedTicket(spn); ok {
+	var skey types.EncryptionKey
+	if tkt, skey, ok := cl.GetCachedTicket(spn); ok {
 		// Already a valid ticket in the cache
-		return tkt, nil
+		return tkt, skey, nil
 	}
 	// Ensure TGT still valid
 	if time.Now().After(cl.Session.EndTime) {
 		err := cl.updateTGT()
 		if err != nil {
-			return tkt, err
+			return tkt, skey, err
 		}
 	}
 	s := strings.Split(spn, "/")
@@ -65,7 +66,7 @@ func (cl *Client) GetServiceTicket(spn string) (types.Ticket, error) {
 	}
 	_, tgsRep, err := cl.TGSExchange(princ, cl.Session.TGT, cl.Session.SessionKey, false)
 	if err != nil {
-		return tkt, err
+		return tkt, skey, err
 	}
 	cl.Cache.AddEntry(
 		tgsRep.Ticket,
@@ -74,5 +75,5 @@ func (cl *Client) GetServiceTicket(spn string) (types.Ticket, error) {
 		tgsRep.DecryptedEncPart.RenewTill,
 		tgsRep.DecryptedEncPart.Key,
 	)
-	return tgsRep.Ticket, nil
+	return tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, nil
 }

+ 6 - 5
client/cache.go

@@ -53,21 +53,22 @@ func (c *Cache) RemoveEntry(spn string) {
 
 // Get a ticket from the cache for the SPN.
 // Only a ticket that is currently valid will be returned.
-func (cl *Client) GetCachedTicket(spn string) (types.Ticket, bool) {
+func (cl *Client) GetCachedTicket(spn string) (types.Ticket, types.EncryptionKey, bool) {
 	if e, ok := cl.Cache.GetEntry(spn); ok {
 		//If within time window of ticket return it
 		if time.Now().After(e.AuthTime) && time.Now().Before(e.EndTime) {
-			return e.Ticket, true
+			return e.Ticket, e.SessionKey, true
 		} else if time.Now().Before(e.RenewTill) {
 			e, err := cl.RenewTicket(e)
 			if err != nil {
-				return e.Ticket, false
+				return e.Ticket, e.SessionKey, false
 			}
-			return e.Ticket, true
+			return e.Ticket, e.SessionKey, true
 		}
 	}
 	var tkt types.Ticket
-	return tkt, false
+	var key types.EncryptionKey
+	return tkt, key, false
 }
 
 // Renew a cache entry ticket

+ 27 - 0
client/http.go

@@ -0,0 +1,27 @@
+package client
+
+import (
+	"github.com/jcmturner/gokrb5/GSSAPI"
+	"fmt"
+	"net/http"
+	"encoding/base64"
+)
+
+func (cl *Client) SetKRB5NegotiationHeader(HTTPReq *http.Request, spn string) error {
+	tkt, skey, err := cl.GetServiceTicket(spn)
+	if err != nil {
+		return fmt.Errorf("Could not get service ticket: %v", err)
+	}
+	negTokenInit, err := GSSAPI.NewNegTokenInitKrb5(*cl.Config, cl.Credentials.CName, tkt, skey)
+	if err != nil {
+		return fmt.Errorf("Could not create NegTokenInit: %v", err)
+	}
+	nb, err := negTokenInit.Marshal()
+	if err != nil {
+		return fmt.Errorf("Could marshal NegTokenInit: %v", err)
+	}
+
+	hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb)
+	HTTPReq.Header.Set("Authorization", hs)
+	return nil
+}

+ 14 - 5
client/network.go

@@ -40,7 +40,7 @@ func (cl *Client) SendToKDC(b []byte) ([]byte, error) {
 		if len(rb) < 1 {
 			return rb, fmt.Errorf("No response data from KDC %v", kdc)
 		}
-		return rb, nil
+		return checkForKRBError(rb)
 	}
 	if len(b) <= cl.Config.LibDefaults.Udp_preference_limit {
 		//Try UDP first, TCP second
@@ -63,7 +63,7 @@ func (cl *Client) SendToKDC(b []byte) ([]byte, error) {
 		if len(rb) < 1 {
 			return rb, fmt.Errorf("No response data from KDC %v", kdc)
 		}
-		return rb, nil
+		return checkForKRBError(rb)
 	}
 	//Try TCP first, UDP second
 	rb, errtcp := sendTCP(kdc, b)
@@ -76,7 +76,7 @@ func (cl *Client) SendToKDC(b []byte) ([]byte, error) {
 	if len(rb) < 1 {
 		return rb, fmt.Errorf("No response data from KDC %v", kdc)
 	}
-	return rb, nil
+	return checkForKRBError(rb)
 }
 
 // Send the bytes to the KDC over UDP.
@@ -102,7 +102,7 @@ func sendUDP(kdc string, b []byte) ([]byte, error) {
 	if err != nil {
 		return r, fmt.Errorf("Sending over UDP failed: %v", err)
 	}
-	return r, nil
+	return checkForKRBError(r)
 }
 
 // Send the bytes to the KDC over TCP.
@@ -128,5 +128,14 @@ func sendTCP(kdc string, b []byte) ([]byte, error) {
 	if err != nil {
 		return r, fmt.Errorf("Sending over TCP failed: %v", err)
 	}
-	return r, nil
+	return checkForKRBError(r)
 }
+
+func checkForKRBError(b []byte) ([]byte, error) {
+	var KRBErr messages.KRBError
+	if err := KRBErr.Unmarshal(b); err == nil {
+		return b, KRBErr
+	} else {
+	}
+	return b, nil
+}

+ 21 - 2
debug.go

@@ -9,6 +9,7 @@ import (
 	"github.com/jcmturner/gokrb5/testdata"
 	"os"
 	"time"
+	"net/http"
 )
 
 const krb5conf = `[libdefaults]
@@ -33,7 +34,8 @@ const krb5conf = `[libdefaults]
  `
 
 func main() {
-	runClient()
+	httpRequest()
+	//runClient()
 }
 
 func runClient() {
@@ -49,7 +51,7 @@ func runClient() {
 	}
 	cl.EnableAutoSessionRenewal()
 	for i := 0; i < 15; i++ {
-		tkt, err := cl.GetServiceTicket("HTTP/host.test.gokrb5")
+		tkt, _, err := cl.GetServiceTicket("HTTP/host.test.gokrb5")
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "Error on TGS_REQ: %v\n", err)
 		} else {
@@ -58,3 +60,20 @@ func runClient() {
 		time.Sleep(time.Duration(1) * time.Minute)
 	}
 }
+
+func httpRequest() {
+	b, err := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
+	kt, _ := keytab.Parse(b)
+	c, _ := config.NewConfigFromString(krb5conf)
+	cl := client.NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
+	cl.WithConfig(c)
+
+	err = cl.Login()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error on AS_REQ: %v\n", err)
+	}
+	r, _ := http.NewRequest("GET", "http://10.80.88.90/index.html", nil)
+	cl.SetKRB5NegotiationHeader(r, "HTTP/host.test.gokrb5")
+	httpResp, err := http.DefaultClient.Do(r)
+	fmt.Fprintf(os.Stderr, "RESPONSE CODE: %v\n", httpResp.StatusCode)
+}

+ 4 - 7
messages/KDCReq.go

@@ -80,7 +80,7 @@ type KDCReqBody struct {
 }
 
 // Generate a new KRB_AS_REQ struct.
-func NewASReq(c *config.Config, username string) ASReq {
+func NewASReq(c *config.Config, cname types.PrincipalName) ASReq {
 	pas := types.PADataSequence{
 		types.PAData{
 			PADataType: patype.PA_REQ_ENC_PA_REP,
@@ -97,10 +97,7 @@ func NewASReq(c *config.Config, username string) ASReq {
 			ReqBody: KDCReqBody{
 				KDCOptions: c.LibDefaults.Kdc_default_options,
 				Realm:      c.LibDefaults.Default_realm,
-				CName: types.PrincipalName{
-					NameType:   nametype.KRB_NT_PRINCIPAL,
-					NameString: []string{username},
-				},
+				CName: cname,
 				SName: types.PrincipalName{
 					NameType:   nametype.KRB_NT_SRV_INST,
 					NameString: []string{"krbtgt", c.LibDefaults.Default_realm},
@@ -131,7 +128,7 @@ func NewASReq(c *config.Config, username string) ASReq {
 }
 
 // Generate a new KRB_TGS_REQ struct.
-func NewTGSReq(username string, c *config.Config, tkt types.Ticket, sessionKey types.EncryptionKey, spn types.PrincipalName, renewal bool) (TGSReq, error) {
+func NewTGSReq(cname types.PrincipalName, c *config.Config, tkt types.Ticket, sessionKey types.EncryptionKey, spn types.PrincipalName, renewal bool) (TGSReq, error) {
 	nonce := int(rand.Int31())
 	t := time.Now()
 	a := TGSReq{
@@ -167,7 +164,7 @@ func NewTGSReq(username string, c *config.Config, tkt types.Ticket, sessionKey t
 		types.SetFlag(&a.ReqBody.KDCOptions, types.Renew)
 		types.SetFlag(&a.ReqBody.KDCOptions, types.Renewable)
 	}
-	auth := types.NewAuthenticator(c.LibDefaults.Default_realm, username)
+	auth := types.NewAuthenticator(c.LibDefaults.Default_realm, cname)
 	// Add the CName to make validation of the reply easier
 	a.ReqBody.CName = auth.CName
 	b, err := a.ReqBody.Marshal()

+ 2 - 6
types/Authenticator.go

@@ -5,7 +5,6 @@ import (
 	"github.com/jcmturner/asn1"
 	"github.com/jcmturner/gokrb5/iana"
 	"github.com/jcmturner/gokrb5/iana/asnAppTag"
-	"github.com/jcmturner/gokrb5/iana/nametype"
 	"time"
 	"github.com/jcmturner/gokrb5/asn1tools"
 )
@@ -42,15 +41,12 @@ type Authenticator struct {
 	AuthorizationData AuthorizationData `asn1:"explicit,optional,tag:8"`
 }
 
-func NewAuthenticator(realm, username string) Authenticator {
+func NewAuthenticator(realm string, cname PrincipalName) Authenticator {
 	t := time.Now()
 	return Authenticator{
 		AVNO:   iana.PVNO,
 		CRealm: realm,
-		CName: PrincipalName{
-			NameType:   nametype.KRB_NT_PRINCIPAL,
-			NameString: []string{username},
-		},
+		CName: cname,
 		Cksum: Checksum{},
 		Cusec: int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)),
 		CTime: t,