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

beginning of client password change

Jonathan Turner 8 лет назад
Родитель
Сommit
24a3d5e57e

+ 2 - 2
client/client_dns_test.go

@@ -20,7 +20,7 @@ func TestResolveKDC(t *testing.T) {
 	c.LibDefaults.DNSLookupKDC = true
 	var cl Client
 	cl.WithConfig(c)
-	count, res, err := cl.resolveKDC(c.LibDefaults.DefaultRealm, true)
+	count, res, err := cl.Config.GetKDCs(c.LibDefaults.DefaultRealm, true)
 	if err != nil {
 		t.Errorf("error resolving KDC via DNS TCP: %v", err)
 	}
@@ -44,7 +44,7 @@ func TestResolveKDC(t *testing.T) {
 		assert.True(t, found, "Record %s not found in results", s)
 	}
 	c.LibDefaults.DNSLookupKDC = false
-	_, res, err = cl.resolveKDC(c.LibDefaults.DefaultRealm, true)
+	_, res, err = cl.Config.GetKDCs(c.LibDefaults.DefaultRealm, true)
 	if err != nil {
 		t.Errorf("error resolving KDCs from config: %v", err)
 	}

+ 31 - 0
client/client_integration_test.go

@@ -624,3 +624,34 @@ func TestGetServiceTicketFromCCacheWithoutKDC(t *testing.T) {
 	}
 	assert.Equal(t, http.StatusOK, httpResp.StatusCode, "status code in response to client SPNEGO request not as expected")
 }
+
+func TestClient_ChangePasswd(t *testing.T) {
+	b, err := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
+	kt, _ := keytab.Parse(b)
+	c, _ := config.NewConfigFromString(testdata.TEST_KRB5CONF)
+	addr := os.Getenv("TEST_KDC_ADDR")
+	if addr == "" {
+		addr = testdata.TEST_KDC_ADDR
+	}
+	c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
+	c.Realms[0].KPasswdServer = []string{addr + ":464"}
+	cl := NewClientWithKeytab("testuser1", "TEST.GOKRB5", kt)
+	cl.WithConfig(c)
+
+	err = cl.Login()
+	if err != nil {
+		t.Fatalf("error on login: %v", err)
+	}
+	ok, err := cl.ChangePasswd("newpassword")
+	if err != nil {
+		t.Fatalf("error changing password: %v", err)
+	}
+	assert.True(t, ok, "password was not changed")
+
+	cl = NewClientWithPassword("testuser1", "TEST.GOKRB5", "newpassword")
+	cl.WithConfig(c)
+	err = cl.Login()
+	if err != nil {
+		t.Fatalf("error on login with new password: %v", err)
+	}
+}

+ 40 - 86
client/network.go

@@ -6,80 +6,19 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"math/rand"
 	"net"
-	"strconv"
-	"strings"
 	"time"
 
-	"gopkg.in/jcmturner/dnsutils.v1"
 	"gopkg.in/jcmturner/gokrb5.v4/iana/errorcode"
 	"gopkg.in/jcmturner/gokrb5.v4/messages"
 )
 
-func (cl *Client) resolveKDC(realm string, tcp bool) (int, map[int]string, error) {
-	kdcs := make(map[int]string)
-	var count int
-
-	// Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf.
-	if cl.Config.LibDefaults.DNSLookupKDC {
-		proto := "udp"
-		if tcp {
-			proto = "tcp"
-		}
-		c, addrs, err := dnsutils.OrderedSRV("kerberos", proto, realm)
-		if err != nil {
-			return count, kdcs, err
-		}
-		if len(addrs) < 1 {
-			return count, kdcs, fmt.Errorf("no KDC SRV records found for realm %s", realm)
-		}
-		count = c
-		for k, v := range addrs {
-			kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
-		}
-	} else {
-		// Get the KDCs from the krb5.conf an order them randomly for preference.
-		var ks []string
-		for _, r := range cl.Config.Realms {
-			if r.Realm == realm {
-				ks = r.KDC
-				break
-			}
-		}
-		count = len(ks)
-		if count < 1 {
-			return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm)
-		}
-		i := 1
-		if count > 1 {
-			l := len(ks)
-			for l > 0 {
-				ri := rand.Intn(l)
-				kdcs[i] = ks[ri]
-				if l > 1 {
-					// Remove the entry from the source slice by swapping with the last entry and truncating
-					ks[len(ks)-1], ks[ri] = ks[ri], ks[len(ks)-1]
-					ks = ks[:len(ks)-1]
-					l = len(ks)
-				} else {
-					l = 0
-				}
-				i++
-			}
-		} else {
-			kdcs[i] = ks[0]
-		}
-	}
-	return count, kdcs, nil
-}
-
 // SendToKDC performs network actions to send data to the KDC.
 func (cl *Client) SendToKDC(b []byte, realm string) ([]byte, error) {
 	var rb []byte
 	if cl.Config.LibDefaults.UDPPreferenceLimit == 1 {
 		//1 means we should always use TCP
-		rb, errtcp := cl.sendTCP(realm, b)
+		rb, errtcp := cl.sendKDCTCP(realm, b)
 		if errtcp != nil {
 			if e, ok := errtcp.(messages.KRBError); ok {
 				return rb, e
@@ -90,7 +29,7 @@ func (cl *Client) SendToKDC(b []byte, realm string) ([]byte, error) {
 	}
 	if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
 		//Try UDP first, TCP second
-		rb, errudp := cl.sendUDP(realm, b)
+		rb, errudp := cl.sendKDCUDP(realm, b)
 		if errudp != nil {
 			if e, ok := errudp.(messages.KRBError); ok && e.ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG {
 				// Got a KRBError from KDC
@@ -98,7 +37,7 @@ func (cl *Client) SendToKDC(b []byte, realm string) ([]byte, error) {
 				return rb, e
 			}
 			// Try TCP
-			r, errtcp := cl.sendTCP(realm, b)
+			r, errtcp := cl.sendKDCTCP(realm, b)
 			if errtcp != nil {
 				if e, ok := errtcp.(messages.KRBError); ok {
 					// Got a KRBError
@@ -111,13 +50,13 @@ func (cl *Client) SendToKDC(b []byte, realm string) ([]byte, error) {
 		return rb, nil
 	}
 	//Try TCP first, UDP second
-	rb, errtcp := cl.sendTCP(realm, b)
+	rb, errtcp := cl.sendKDCTCP(realm, b)
 	if errtcp != nil {
 		if e, ok := errtcp.(messages.KRBError); ok {
 			// Got a KRBError from KDC so returning and not trying UDP.
 			return rb, e
 		}
-		rb, errudp := cl.sendUDP(realm, b)
+		rb, errudp := cl.sendKDCUDP(realm, b)
 		if errudp != nil {
 			if e, ok := errudp.(messages.KRBError); ok {
 				// Got a KRBError
@@ -168,9 +107,9 @@ func dialKDCTCP(count int, kdcs map[int]string) (conn *net.TCPConn, err error) {
 }
 
 // Send the bytes to the KDC over UDP.
-func (cl *Client) sendUDP(realm string, b []byte) ([]byte, error) {
+func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) {
 	var r []byte
-	count, kdcs, err := cl.resolveKDC(realm, false)
+	count, kdcs, err := cl.Config.GetKDCs(realm, false)
 	if err != nil {
 		return r, err
 	}
@@ -178,10 +117,34 @@ func (cl *Client) sendUDP(realm string, b []byte) ([]byte, error) {
 	if err != nil {
 		return r, err
 	}
+	r, err = cl.sendUDP(conn, b)
+	return checkForKRBError(r)
+}
+
+func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) {
+	var r []byte
+	count, kdcs, err := cl.Config.GetKDCs(realm, true)
+	if err != nil {
+		return r, err
+	}
+	conn, err := dialKDCTCP(count, kdcs)
+	if err != nil {
+		return r, err
+	}
+	rb, err := cl.sendTCP(conn, b)
+	if err != nil {
+		return r, err
+	}
+	return checkForKRBError(rb)
+}
+
+// Send the bytes over UDP.
+func (cl *Client) sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
+	var r []byte
 	defer conn.Close()
-	_, err = conn.Write(b)
+	_, err := conn.Write(b)
 	if err != nil {
-		return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
+		return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err)
 	}
 	udpbuf := make([]byte, 4096)
 	n, _, err := conn.ReadFrom(udpbuf)
@@ -190,24 +153,15 @@ func (cl *Client) sendUDP(realm string, b []byte) ([]byte, error) {
 		return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
 	}
 	if len(r) < 1 {
-		return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
+		return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String())
 	}
-	return checkForKRBError(r)
+	return r, nil
 }
 
-// Send the bytes to the KDC over TCP.
-func (cl *Client) sendTCP(realm string, b []byte) ([]byte, error) {
-	var r []byte
-	count, kdcs, err := cl.resolveKDC(realm, true)
-	if err != nil {
-		return r, err
-	}
-	conn, err := dialKDCTCP(count, kdcs)
-	if err != nil {
-		return r, err
-	}
+// Send the bytes over TCP.
+func (cl *Client) sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
 	defer conn.Close()
-
+	var r []byte
 	/*
 		RFC https://tools.ietf.org/html/rfc4120#section-7.2.2
 		Each request (KRB_KDC_REQ) and response (KRB_KDC_REP or KRB_ERROR)
@@ -224,7 +178,7 @@ func (cl *Client) sendTCP(realm string, b []byte) ([]byte, error) {
 	binary.Write(&buf, binary.BigEndian, uint32(len(b)))
 	b = append(buf.Bytes(), b...)
 
-	_, err = conn.Write(b)
+	_, err := conn.Write(b)
 	if err != nil {
 		return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
 	}
@@ -244,7 +198,7 @@ func (cl *Client) sendTCP(realm string, b []byte) ([]byte, error) {
 	if len(rb) < 1 {
 		return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
 	}
-	return checkForKRBError(rb)
+	return rb, nil
 }
 
 func checkForKRBError(b []byte) ([]byte, error) {

+ 83 - 0
client/passwd.go

@@ -0,0 +1,83 @@
+package client
+
+import (
+	"fmt"
+	"net"
+
+	"gopkg.in/jcmturner/gokrb5.v4/kadmin"
+)
+
+const (
+	passwdChangeSPN = "kadmin/changepw"
+
+	KRB5_KPASSWD_SUCCESS             = 0
+	KRB5_KPASSWD_MALFORMED           = 1
+	KRB5_KPASSWD_HARDERROR           = 2
+	KRB5_KPASSWD_AUTHERROR           = 3
+	KRB5_KPASSWD_SOFTERROR           = 4
+	KRB5_KPASSWD_ACCESSDENIED        = 5
+	KRB5_KPASSWD_BAD_VERSION         = 6
+	KRB5_KPASSWD_INITIAL_FLAG_NEEDED = 7
+)
+
+func (cl *Client) ChangePasswd(newPasswd string) (bool, error) {
+	tkt, skey, err := cl.GetServiceTicket(passwdChangeSPN)
+	if err != nil {
+		return false, fmt.Errorf("could not get service ticket: %v", err)
+	}
+
+	msg, key, err := kadmin.ChangePasswdMsg(cl.Credentials.CName, cl.Credentials.Realm, newPasswd, tkt, skey)
+	r, err := cl.sendToKPasswd(msg)
+	err = r.Decrypt(key)
+	if err != nil {
+		return false, err
+	}
+	if r.ResultCode != KRB5_KPASSWD_SUCCESS {
+		return false, fmt.Errorf("error response from kdamin: %s", r.Result)
+	}
+	return true, nil
+}
+
+func (cl *Client) sendToKPasswd(msg kadmin.Request) (r kadmin.Reply, err error) {
+	_, kps, err := cl.Config.GetKpasswdServers(cl.Credentials.Realm, true)
+	if err != nil {
+		return
+	}
+	addr := kps[1]
+	b, err := msg.Marshal()
+	if err != nil {
+		return
+	}
+	if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
+		return cl.sendKPasswdUDP(b, addr)
+	}
+	return cl.sendKPasswdTCP(b, addr)
+}
+
+func (cl *Client) sendKPasswdTCP(b []byte, kadmindAddr string) (r kadmin.Reply, err error) {
+	tcpAddr, err := net.ResolveTCPAddr("tcp", kadmindAddr)
+	if err != nil {
+		return
+	}
+	conn, err := net.DialTCP("tcp", nil, tcpAddr)
+	if err != nil {
+		return
+	}
+	rb, err := cl.sendTCP(conn, b)
+	err = r.Unmarshal(rb)
+	return
+}
+
+func (cl *Client) sendKPasswdUDP(b []byte, kadmindAddr string) (r kadmin.Reply, err error) {
+	udpAddr, err := net.ResolveUDPAddr("udp", kadmindAddr)
+	if err != nil {
+		return
+	}
+	conn, err := net.DialUDP("udp", nil, udpAddr)
+	if err != nil {
+		return
+	}
+	rb, err := cl.sendUDP(conn, b)
+	err = r.Unmarshal(rb)
+	return
+}

+ 131 - 0
config/hosts.go

@@ -0,0 +1,131 @@
+package config
+
+import (
+	"fmt"
+	"math/rand"
+	"net"
+	"strconv"
+	"strings"
+
+	"gopkg.in/jcmturner/dnsutils.v1"
+)
+
+// GetKDCs returns the count of KDCs available and a map of KDC host names keyed on preference order.
+func (c *Config) GetKDCs(realm string, tcp bool) (int, map[int]string, error) {
+	kdcs := make(map[int]string)
+	var count int
+
+	// Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf.
+	if c.LibDefaults.DNSLookupKDC {
+		proto := "udp"
+		if tcp {
+			proto = "tcp"
+		}
+		c, addrs, err := dnsutils.OrderedSRV("kerberos", proto, realm)
+		if err != nil {
+			return count, kdcs, err
+		}
+		if len(addrs) < 1 {
+			return count, kdcs, fmt.Errorf("no KDC SRV records found for realm %s", realm)
+		}
+		count = c
+		for k, v := range addrs {
+			kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
+		}
+	} else {
+		// Get the KDCs from the krb5.conf an order them randomly for preference.
+		var ks []string
+		for _, r := range c.Realms {
+			if r.Realm == realm {
+				ks = r.KDC
+				break
+			}
+		}
+		count = len(ks)
+		if count < 1 {
+			return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm)
+		}
+		kdcs = randServOrder(ks)
+	}
+	return count, kdcs, nil
+}
+
+// GetKpasswdServers returns the count of kpasswd servers available and a map of kpasswd host names keyed on preference order.
+// https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms - see kpasswd_server section
+func (c *Config) GetKpasswdServers(realm string, tcp bool) (int, map[int]string, error) {
+	kdcs := make(map[int]string)
+	var count int
+
+	// Use DNS to resolve kerberos SRV records if configured to do so in krb5.conf.
+	if c.LibDefaults.DNSLookupKDC {
+		proto := "udp"
+		if tcp {
+			proto = "tcp"
+		}
+		c, addrs, err := dnsutils.OrderedSRV("kpasswd", proto, realm)
+		if err != nil {
+			return count, kdcs, err
+		}
+		if c < 1 {
+			c, addrs, err = dnsutils.OrderedSRV("kerberos-adm", proto, realm)
+		}
+		if len(addrs) < 1 {
+			return count, kdcs, fmt.Errorf("no kpasswd or kadmin SRV records found for realm %s", realm)
+		}
+		count = c
+		for k, v := range addrs {
+			kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
+		}
+	} else {
+		// Get the KDCs from the krb5.conf an order them randomly for preference.
+		var ks []string
+		var ka []string
+		for _, r := range c.Realms {
+			if r.Realm == realm {
+				ks = r.KPasswdServer
+				ka = r.AdminServer
+				break
+			}
+		}
+		if len(ks) < 1 {
+			for _, k := range ka {
+				h, _, err := net.SplitHostPort(k)
+				if err != nil {
+					continue
+				}
+				ks = append(ks, h+":464")
+			}
+		}
+		count = len(ks)
+		if count < 1 {
+			return count, kdcs, fmt.Errorf("no kpasswd or kadmin defined in configuration for realm %s", realm)
+		}
+		kdcs = randServOrder(ks)
+	}
+	return count, kdcs, nil
+}
+
+func randServOrder(ks []string) map[int]string {
+	kdcs := make(map[int]string)
+	count := len(ks)
+	i := 1
+	if count > 1 {
+		l := len(ks)
+		for l > 0 {
+			ri := rand.Intn(l)
+			kdcs[i] = ks[ri]
+			if l > 1 {
+				// Remove the entry from the source slice by swapping with the last entry and truncating
+				ks[len(ks)-1], ks[ri] = ks[ri], ks[len(ks)-1]
+				ks = ks[:len(ks)-1]
+				l = len(ks)
+			} else {
+				l = 0
+			}
+			i++
+		}
+	} else {
+		kdcs[i] = ks[0]
+	}
+	return kdcs
+}

+ 58 - 2
kadmin/message.go

@@ -1,6 +1,7 @@
 package kadmin
 
 import (
+	"bytes"
 	"encoding/binary"
 	"encoding/hex"
 	"errors"
@@ -8,18 +9,28 @@ import (
 	"math"
 
 	"gopkg.in/jcmturner/gokrb5.v4/messages"
+	"gopkg.in/jcmturner/gokrb5.v4/types"
 )
 
 const (
 	verisonHex = "0xff80"
 )
 
-type Message struct {
+type Request struct {
 	APREQ   messages.APReq
 	KRBPriv messages.KRBPriv
 }
 
-func (m *Message) Marshal() (b []byte, err error) {
+type Reply struct {
+	APREP      messages.APRep
+	KRBPriv    messages.KRBPriv
+	KRBError   messages.KRBError
+	IsKRBError bool
+	ResultCode uint16
+	Result     string
+}
+
+func (m *Request) Marshal() (b []byte, err error) {
 	vb, _ := hex.DecodeString(verisonHex)
 	b = append(b, vb...)
 	ab, e := m.APREQ.Marshal()
@@ -50,3 +61,48 @@ func (m *Message) Marshal() (b []byte, err error) {
 	b = append(ml, b...)
 	return
 }
+
+func (m *Reply) Unmarshal(b []byte) error {
+	msgLen := int(binary.BigEndian.Uint16(b[0:2]))
+	v := int(binary.BigEndian.Uint16(b[2:4]))
+	if v != 1 {
+		return fmt.Errorf("kadmin reply has incorrect protocol version number: %d", v)
+	}
+	APRepLen := int(binary.BigEndian.Uint16(b[4:6]))
+	if APRepLen != 0 {
+		err := m.APREP.Unmarshal(b[6 : 6+APRepLen])
+		if err != nil {
+			return err
+		}
+		err = m.KRBPriv.Unmarshal(b[6+APRepLen : msgLen])
+		if err != nil {
+			return err
+		}
+	} else {
+		m.IsKRBError = true
+		m.KRBError.Unmarshal(b[6:msgLen])
+		m.ResultCode, m.Result = parseResponse(m.KRBError.EData)
+	}
+	return nil
+}
+
+func parseResponse(b []byte) (c uint16, s string) {
+	c = binary.BigEndian.Uint16(b[0:2])
+	buf := bytes.NewBuffer(b[2:])
+	m := make([]byte, len(b)-2)
+	binary.Read(buf, binary.BigEndian, &m)
+	s = string(m)
+	return
+}
+
+func (m *Reply) Decrypt(key types.EncryptionKey) error {
+	if m.IsKRBError {
+		return m.KRBError
+	}
+	err := m.KRBPriv.DecryptEncPart(key)
+	if err != nil {
+		return err
+	}
+	m.ResultCode, m.Result = parseResponse(m.KRBPriv.DecryptedEncPart.UserData)
+	return nil
+}

+ 21 - 15
kadmin/passwd.go

@@ -7,11 +7,7 @@ import (
 	"gopkg.in/jcmturner/gokrb5.v4/types"
 )
 
-const (
-	spn = "kadmin/changepw@%s"
-)
-
-func ChangePasswdMsg(cname types.PrincipalName, realm, password string, tkt messages.Ticket, sessionKey types.EncryptionKey) (Message, error) {
+func ChangePasswdMsg(cname types.PrincipalName, realm, password string, tkt messages.Ticket, sessionKey types.EncryptionKey) (r Request, k types.EncryptionKey, err error) {
 	// Create change password data struct and marshal to bytes
 	chgpasswd := ChangePasswdData{
 		NewPasswd: []byte(password),
@@ -20,43 +16,53 @@ func ChangePasswdMsg(cname types.PrincipalName, realm, password string, tkt mess
 	}
 	chpwdb, err := chgpasswd.Marshal()
 	if err != nil {
-		return Message{}, krberror.Errorf(err, krberror.KRBMsgError, "error marshaling change passwd data")
+		err = krberror.Errorf(err, krberror.KRBMsgError, "error marshaling change passwd data")
+		return
 	}
 
 	// Generate authenticator
 	auth, err := types.NewAuthenticator(realm, cname)
 	if err != nil {
-		return Message{}, krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
+		err = krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
+		return
 	}
 	etype, err := crypto.GetEtype(sessionKey.KeyType)
 	if err != nil {
-		return Message{}, krberror.Errorf(err, krberror.KRBMsgError, "error generating subkey etype")
+		err = krberror.Errorf(err, krberror.KRBMsgError, "error generating subkey etype")
+		return
 	}
 	err = auth.GenerateSeqNumberAndSubKey(sessionKey.KeyType, etype.GetKeyByteSize())
 	if err != nil {
-		return Message{}, krberror.Errorf(err, krberror.KRBMsgError, "error generating subkey")
+		err = krberror.Errorf(err, krberror.KRBMsgError, "error generating subkey")
+		return
 	}
+	k = auth.SubKey
 
 	// Generate AP_REQ
 	APreq, err := messages.NewAPReq(tkt, sessionKey, auth)
 	if err != nil {
-		return Message{}, err
+		return
 	}
 
 	// Form the KRBPriv encpart data
 	//TODO set the SAddress field???
 	kp := messages.EncKrbPrivPart{
+		UserData:       chpwdb,
 		Timestamp:      auth.CTime,
 		Usec:           auth.Cusec,
 		SequenceNumber: auth.SeqNumber,
 	}
-	_, kp.UserData, err = etype.EncryptData(auth.SubKey.KeyValue, chpwdb)
+	kpriv := messages.NewKRBPriv(kp)
+	err = kpriv.EncryptEncPart(k)
+	//_, kp.UserData, err = etype.EncryptData(auth.SubKey.KeyValue, chpwdb)
 	if err != nil {
-		return Message{}, krberror.Errorf(err, krberror.EncryptingError, "error encrypting change passwd data")
+		err = krberror.Errorf(err, krberror.EncryptingError, "error encrypting change passwd data")
+		return
 	}
 
-	return Message{
+	r = Request{
 		APREQ:   APreq,
-		KRBPriv: messages.NewKRBPriv(kp),
-	}, nil
+		KRBPriv: kpriv,
+	}
+	return
 }

+ 14 - 8
messages/KDCReq.go

@@ -83,8 +83,17 @@ type KDCReqBody struct {
 	AdditionalTickets []Ticket            `asn1:"explicit,optional,tag:11"`
 }
 
-// NewASReq generates a new KRB_AS_REQ struct.
+// NewASReq generates a new KRB_AS_REQ struct for a TGT request.
 func NewASReq(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) {
+	sname := types.PrincipalName{
+		NameType:   nametype.KRB_NT_SRV_INST,
+		NameString: []string{"krbtgt", realm},
+	}
+	return NewASReqForSName(realm, c, cname, sname)
+}
+
+// NewASReqSNAME generates a new KRB_AS_REQ struct for a given SNAME.
+func NewASReqForSName(realm string, c *config.Config, cname, sname types.PrincipalName) (ASReq, error) {
 	nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
 	if err != nil {
 		return ASReq{}, err
@@ -103,13 +112,10 @@ func NewASReq(realm string, c *config.Config, cname types.PrincipalName) (ASReq,
 				KDCOptions: kopts,
 				Realm:      realm,
 				CName:      cname,
-				SName: types.PrincipalName{
-					NameType:   nametype.KRB_NT_SRV_INST,
-					NameString: []string{"krbtgt", realm},
-				},
-				Till:  t.Add(c.LibDefaults.TicketLifetime),
-				Nonce: int(nonce.Int64()),
-				EType: c.LibDefaults.DefaultTktEnctypeIDs,
+				SName:      sname,
+				Till:       t.Add(c.LibDefaults.TicketLifetime),
+				Nonce:      int(nonce.Int64()),
+				EType:      c.LibDefaults.DefaultTktEnctypeIDs,
 			},
 		},
 	}

+ 13 - 0
messages/KRBPriv.go

@@ -93,3 +93,16 @@ func (k *KRBPriv) EncryptEncPart(key types.EncryptionKey) error {
 	}
 	return nil
 }
+
+// DecryptEncPart decrypts the encrypted part of the KRBPriv message.
+func (k *KRBPriv) DecryptEncPart(key types.EncryptionKey) error {
+	b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.KRB_PRIV_ENCPART)
+	if err != nil {
+		return fmt.Errorf("error decrypting KRBPriv EncPart: %v", err)
+	}
+	err = k.DecryptedEncPart.Unmarshal(b)
+	if err != nil {
+		return fmt.Errorf("error unmarshaling encrypted part: %v", err)
+	}
+	return nil
+}

+ 1 - 0
testenv/krb5kdc-vagrant/kadm5.acl

@@ -1,4 +1,5 @@
 __ADMIN_USER__@__REALM__	*
 */admin@__REALM__	*
 */*@__REALM__		i
+*@__REALM__     c   *1@__REALM__
 *@__REALM__		i

+ 2 - 1
testenv/krb5kdc-vagrant/kdc-setup.sh

@@ -60,4 +60,5 @@ create_entropy &
 
 
 systemctl restart krb5kdc
-systemctl enable krb5kdc
+systemctl start kadmin
+systemctl enable krb5kdc kadmin