[VOL-4291] Rw-core updates for gRPC migration

Change-Id: I8d5a554409115b29318089671ca4e1ab3fa98810
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/LICENSE b/vendor/github.com/jcmturner/gokrb5/v8/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/asn1tools/tools.go b/vendor/github.com/jcmturner/gokrb5/v8/asn1tools/tools.go
new file mode 100644
index 0000000..f27740b
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/asn1tools/tools.go
@@ -0,0 +1,86 @@
+// Package asn1tools provides tools for managing ASN1 marshaled data.
+package asn1tools
+
+import (
+	"github.com/jcmturner/gofork/encoding/asn1"
+)
+
+// MarshalLengthBytes returns the ASN1 encoded bytes for the length 'l'
+//
+// There are two forms: short (for lengths between 0 and 127), and long definite (for lengths between 0 and 2^1008 -1).
+//
+// Short form: One octet. Bit 8 has value "0" and bits 7-1 give the length.
+//
+// Long form: Two to 127 octets. Bit 8 of first octet has value "1" and bits 7-1 give the number of additional length octets. Second and following octets give the length, base 256, most significant digit first.
+func MarshalLengthBytes(l int) []byte {
+	if l <= 127 {
+		return []byte{byte(l)}
+	}
+	var b []byte
+	p := 1
+	for i := 1; i < 127; {
+		b = append([]byte{byte((l % (p * 256)) / p)}, b...)
+		p = p * 256
+		l = l - l%p
+		if l <= 0 {
+			break
+		}
+	}
+	return append([]byte{byte(128 + len(b))}, b...)
+}
+
+// GetLengthFromASN returns the length of a slice of ASN1 encoded bytes from the ASN1 length header it contains.
+func GetLengthFromASN(b []byte) int {
+	if int(b[1]) <= 127 {
+		return int(b[1])
+	}
+	// The bytes that indicate the length
+	lb := b[2 : 2+int(b[1])-128]
+	base := 1
+	l := 0
+	for i := len(lb) - 1; i >= 0; i-- {
+		l += int(lb[i]) * base
+		base = base * 256
+	}
+	return l
+}
+
+// GetNumberBytesInLengthHeader returns the number of bytes in the ASn1 header that indicate the length.
+func GetNumberBytesInLengthHeader(b []byte) int {
+	if int(b[1]) <= 127 {
+		return 1
+	}
+	// The bytes that indicate the length
+	return 1 + int(b[1]) - 128
+}
+
+// AddASNAppTag adds an ASN1 encoding application tag value to the raw bytes provided.
+func AddASNAppTag(b []byte, tag int) []byte {
+	r := asn1.RawValue{
+		Class:      asn1.ClassApplication,
+		IsCompound: true,
+		Tag:        tag,
+		Bytes:      b,
+	}
+	ab, _ := asn1.Marshal(r)
+	return ab
+}
+
+/*
+// The Marshal method of golang's asn1 package does not enable you to define wrapping the output in an application tag.
+// This method adds that wrapping tag.
+func AddASNAppTag(b []byte, tag int) []byte {
+	// The ASN1 wrapping consists of 2 bytes:
+	// 1st byte -> Identifier Octet - Application Tag
+	// 2nd byte -> The length (this will be the size indicated in the input bytes + 2 for the additional bytes we add here.
+	// Application Tag:
+	//| Bit:        | 8                            | 7                          | 6                                         | 5 | 4 | 3 | 2 | 1             |
+	//| Value:      | 0                            | 1                          | 1                                         | From the RFC spec 4120        |
+	//| Explanation | Defined by the ASN1 encoding rules for an application tag | A value of 1 indicates a constructed type | The ASN Application tag value |
+	// Therefore the value of the byte is an integer = ( Application tag value + 96 )
+	//b = append(MarshalLengthBytes(int(b[1])+2), b...)
+	b = append(MarshalLengthBytes(len(b)), b...)
+	b = append([]byte{byte(96 + tag)}, b...)
+	return b
+}
+*/
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/ASExchange.go b/vendor/github.com/jcmturner/gokrb5/v8/client/ASExchange.go
new file mode 100644
index 0000000..5becccc
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/ASExchange.go
@@ -0,0 +1,182 @@
+package client
+
+import (
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+	"github.com/jcmturner/gokrb5/v8/iana/errorcode"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/iana/patype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/messages"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// ASExchange performs an AS exchange for the client to retrieve a TGT.
+func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (messages.ASRep, error) {
+	if ok, err := cl.IsConfigured(); !ok {
+		return messages.ASRep{}, krberror.Errorf(err, krberror.ConfigError, "AS Exchange cannot be performed")
+	}
+
+	// Set PAData if required
+	err := setPAData(cl, nil, &ASReq)
+	if err != nil {
+		return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: issue with setting PAData on AS_REQ")
+	}
+
+	b, err := ASReq.Marshal()
+	if err != nil {
+		return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ")
+	}
+	var ASRep messages.ASRep
+
+	rb, err := cl.sendToKDC(b, realm)
+	if err != nil {
+		if e, ok := err.(messages.KRBError); ok {
+			switch e.ErrorCode {
+			case errorcode.KDC_ERR_PREAUTH_REQUIRED, errorcode.KDC_ERR_PREAUTH_FAILED:
+				// From now on assume this client will need to do this pre-auth and set the PAData
+				cl.settings.assumePreAuthentication = true
+				err = setPAData(cl, &e, &ASReq)
+				if err != nil {
+					return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: failed setting AS_REQ PAData for pre-authentication required")
+				}
+				b, err := ASReq.Marshal()
+				if err != nil {
+					return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ with PAData")
+				}
+				rb, err = cl.sendToKDC(b, realm)
+				if err != nil {
+					if _, ok := err.(messages.KRBError); ok {
+						return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
+					}
+					return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
+				}
+			case errorcode.KDC_ERR_WRONG_REALM:
+				// Client referral https://tools.ietf.org/html/rfc6806.html#section-7
+				if referral > 5 {
+					return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of client referrals exceeded")
+				}
+				referral++
+				return cl.ASExchange(e.CRealm, ASReq, referral)
+			default:
+				return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
+			}
+		} else {
+			return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
+		}
+	}
+	err = ASRep.Unmarshal(rb)
+	if err != nil {
+		return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed to process the AS_REP")
+	}
+	if ok, err := ASRep.Verify(cl.Config, cl.Credentials, ASReq); !ok {
+		return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: AS_REP is not valid or client password/keytab incorrect")
+	}
+	return ASRep, nil
+}
+
+// setPAData adds pre-authentication data to the AS_REQ.
+func setPAData(cl *Client, krberr *messages.KRBError, ASReq *messages.ASReq) error {
+	if !cl.settings.DisablePAFXFAST() {
+		pa := types.PAData{PADataType: patype.PA_REQ_ENC_PA_REP}
+		ASReq.PAData = append(ASReq.PAData, pa)
+	}
+	if cl.settings.AssumePreAuthentication() {
+		// Identify the etype to use to encrypt the PA Data
+		var et etype.EType
+		var err error
+		var key types.EncryptionKey
+		var kvno int
+		if krberr == nil {
+			// This is not in response to an error from the KDC. It is preemptive or renewal
+			// There is no KRB Error that tells us the etype to use
+			etn := cl.settings.preAuthEType // Use the etype that may have previously been negotiated
+			if etn == 0 {
+				etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0]) // Resort to config
+			}
+			et, err = crypto.GetEtype(etn)
+			if err != nil {
+				return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
+			}
+			key, kvno, err = cl.Key(et, 0, nil)
+			if err != nil {
+				return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
+			}
+		} else {
+			// Get the etype to use from the PA data in the KRBError e-data
+			et, err = preAuthEType(krberr)
+			if err != nil {
+				return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
+			}
+			cl.settings.preAuthEType = et.GetETypeID() // Set the etype that has been defined for potential future use
+			key, kvno, err = cl.Key(et, 0, krberr)
+			if err != nil {
+				return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
+			}
+		}
+		// Generate the PA data
+		paTSb, err := types.GetPAEncTSEncAsnMarshalled()
+		if err != nil {
+			return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication")
+		}
+		paEncTS, err := crypto.GetEncryptedData(paTSb, key, keyusage.AS_REQ_PA_ENC_TIMESTAMP, kvno)
+		if err != nil {
+			return krberror.Errorf(err, krberror.EncryptingError, "error encrypting pre-authentication timestamp")
+		}
+		pb, err := paEncTS.Marshal()
+		if err != nil {
+			return krberror.Errorf(err, krberror.EncodingError, "error marshaling the PAEncTSEnc encrypted data")
+		}
+		pa := types.PAData{
+			PADataType:  patype.PA_ENC_TIMESTAMP,
+			PADataValue: pb,
+		}
+		// Look for and delete any exiting patype.PA_ENC_TIMESTAMP
+		for i, pa := range ASReq.PAData {
+			if pa.PADataType == patype.PA_ENC_TIMESTAMP {
+				ASReq.PAData[i] = ASReq.PAData[len(ASReq.PAData)-1]
+				ASReq.PAData = ASReq.PAData[:len(ASReq.PAData)-1]
+			}
+		}
+		ASReq.PAData = append(ASReq.PAData, pa)
+	}
+	return nil
+}
+
+// preAuthEType establishes what encryption type to use for pre-authentication from the KRBError returned from the KDC.
+func preAuthEType(krberr *messages.KRBError) (etype etype.EType, err error) {
+	//RFC 4120 5.2.7.5 covers the preference order of ETYPE-INFO2 and ETYPE-INFO.
+	var etypeID int32
+	var pas types.PADataSequence
+	e := pas.Unmarshal(krberr.EData)
+	if e != nil {
+		err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling KRBError data")
+		return
+	}
+Loop:
+	for _, pa := range pas {
+		switch pa.PADataType {
+		case patype.PA_ETYPE_INFO2:
+			info, e := pa.GetETypeInfo2()
+			if e != nil {
+				err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO2 data")
+				return
+			}
+			etypeID = info[0].EType
+			break Loop
+		case patype.PA_ETYPE_INFO:
+			info, e := pa.GetETypeInfo()
+			if e != nil {
+				err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO data")
+				return
+			}
+			etypeID = info[0].EType
+		}
+	}
+	etype, e = crypto.GetEtype(etypeID)
+	if e != nil {
+		err = krberror.Errorf(e, krberror.EncryptingError, "error creating etype")
+		return
+	}
+	return etype, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/TGSExchange.go b/vendor/github.com/jcmturner/gokrb5/v8/client/TGSExchange.go
new file mode 100644
index 0000000..e4571ce
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/TGSExchange.go
@@ -0,0 +1,103 @@
+package client
+
+import (
+	"github.com/jcmturner/gokrb5/v8/iana/flags"
+	"github.com/jcmturner/gokrb5/v8/iana/nametype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/messages"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// TGSREQGenerateAndExchange generates the TGS_REQ and performs a TGS exchange to retrieve a ticket to the specified SPN.
+func (cl *Client) TGSREQGenerateAndExchange(spn types.PrincipalName, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, renewal bool) (tgsReq messages.TGSReq, tgsRep messages.TGSRep, err error) {
+	tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, spn, renewal)
+	if err != nil {
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: failed to generate a new TGS_REQ")
+	}
+	return cl.TGSExchange(tgsReq, kdcRealm, tgsRep.Ticket, sessionKey, 0)
+}
+
+// TGSExchange exchanges the provided TGS_REQ with the KDC to retrieve a TGS_REP.
+// Referrals are automatically handled.
+// The client's cache is updated with the ticket received.
+func (cl *Client) TGSExchange(tgsReq messages.TGSReq, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, referral int) (messages.TGSReq, messages.TGSRep, error) {
+	var tgsRep messages.TGSRep
+	b, err := tgsReq.Marshal()
+	if err != nil {
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to marshal TGS_REQ")
+	}
+	r, err := cl.sendToKDC(b, kdcRealm)
+	if err != nil {
+		if _, ok := err.(messages.KRBError); ok {
+			return tgsReq, tgsRep, krberror.Errorf(err, krberror.KDCError, "TGS Exchange Error: kerberos error response from KDC when requesting for %s", tgsReq.ReqBody.SName.PrincipalNameString())
+		}
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.NetworkingError, "TGS Exchange Error: issue sending TGS_REQ to KDC")
+	}
+	err = tgsRep.Unmarshal(r)
+	if err != nil {
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
+	}
+	err = tgsRep.DecryptEncPart(sessionKey)
+	if err != nil {
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
+	}
+	if ok, err := tgsRep.Verify(cl.Config, tgsReq); !ok {
+		return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: TGS_REP is not valid")
+	}
+
+	if tgsRep.Ticket.SName.NameString[0] == "krbtgt" && !tgsRep.Ticket.SName.Equal(tgsReq.ReqBody.SName) {
+		if referral > 5 {
+			return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: maximum number of referrals exceeded")
+		}
+		// Server referral https://tools.ietf.org/html/rfc6806.html#section-8
+		// The TGS Rep contains a TGT for another domain as the service resides in that domain.
+		cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
+		realm := tgsRep.Ticket.SName.NameString[len(tgsRep.Ticket.SName.NameString)-1]
+		referral++
+		if types.IsFlagSet(&tgsReq.ReqBody.KDCOptions, flags.EncTktInSkey) && len(tgsReq.ReqBody.AdditionalTickets) > 0 {
+			tgsReq, err = messages.NewUser2UserTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal, tgsReq.ReqBody.AdditionalTickets[0])
+			if err != nil {
+				return tgsReq, tgsRep, err
+			}
+		}
+		tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), realm, cl.Config, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, tgsReq.ReqBody.SName, tgsReq.Renewal)
+		if err != nil {
+			return tgsReq, tgsRep, err
+		}
+		return cl.TGSExchange(tgsReq, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, referral)
+	}
+	cl.cache.addEntry(
+		tgsRep.Ticket,
+		tgsRep.DecryptedEncPart.AuthTime,
+		tgsRep.DecryptedEncPart.StartTime,
+		tgsRep.DecryptedEncPart.EndTime,
+		tgsRep.DecryptedEncPart.RenewTill,
+		tgsRep.DecryptedEncPart.Key,
+	)
+	cl.Log("ticket added to cache for %s (EndTime: %v)", tgsRep.Ticket.SName.PrincipalNameString(), tgsRep.DecryptedEncPart.EndTime)
+	return tgsReq, tgsRep, err
+}
+
+// GetServiceTicket makes 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) (messages.Ticket, types.EncryptionKey, error) {
+	var tkt messages.Ticket
+	var skey types.EncryptionKey
+	if tkt, skey, ok := cl.GetCachedTicket(spn); ok {
+		// Already a valid ticket in the cache
+		return tkt, skey, nil
+	}
+	princ := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn)
+	realm := cl.Config.ResolveRealm(princ.NameString[len(princ.NameString)-1])
+
+	tgt, skey, err := cl.sessionTGT(realm)
+	if err != nil {
+		return tkt, skey, err
+	}
+	_, tgsRep, err := cl.TGSREQGenerateAndExchange(princ, realm, tgt, skey, false)
+	if err != nil {
+		return tkt, skey, err
+	}
+	return tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/cache.go b/vendor/github.com/jcmturner/gokrb5/v8/client/cache.go
new file mode 100644
index 0000000..552e73e
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/cache.go
@@ -0,0 +1,134 @@
+package client
+
+import (
+	"encoding/json"
+	"errors"
+	"sort"
+	"sync"
+	"time"
+
+	"github.com/jcmturner/gokrb5/v8/messages"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// Cache for service tickets held by the client.
+type Cache struct {
+	Entries map[string]CacheEntry
+	mux     sync.RWMutex
+}
+
+// CacheEntry holds details for a cache entry.
+type CacheEntry struct {
+	SPN        string
+	Ticket     messages.Ticket `json:"-"`
+	AuthTime   time.Time
+	StartTime  time.Time
+	EndTime    time.Time
+	RenewTill  time.Time
+	SessionKey types.EncryptionKey `json:"-"`
+}
+
+// NewCache creates a new client ticket cache instance.
+func NewCache() *Cache {
+	return &Cache{
+		Entries: map[string]CacheEntry{},
+	}
+}
+
+// getEntry returns a cache entry that matches the SPN.
+func (c *Cache) getEntry(spn string) (CacheEntry, bool) {
+	c.mux.RLock()
+	defer c.mux.RUnlock()
+	e, ok := (*c).Entries[spn]
+	return e, ok
+}
+
+// JSON returns information about the cached service tickets in a JSON format.
+func (c *Cache) JSON() (string, error) {
+	c.mux.RLock()
+	defer c.mux.RUnlock()
+	var es []CacheEntry
+	keys := make([]string, 0, len(c.Entries))
+	for k := range c.Entries {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	for _, k := range keys {
+		es = append(es, c.Entries[k])
+	}
+	b, err := json.MarshalIndent(&es, "", "  ")
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
+
+// addEntry adds a ticket to the cache.
+func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry {
+	spn := tkt.SName.PrincipalNameString()
+	c.mux.Lock()
+	defer c.mux.Unlock()
+	(*c).Entries[spn] = CacheEntry{
+		SPN:        spn,
+		Ticket:     tkt,
+		AuthTime:   authTime,
+		StartTime:  startTime,
+		EndTime:    endTime,
+		RenewTill:  renewTill,
+		SessionKey: sessionKey,
+	}
+	return c.Entries[spn]
+}
+
+// clear deletes all the cache entries
+func (c *Cache) clear() {
+	c.mux.Lock()
+	defer c.mux.Unlock()
+	for k := range c.Entries {
+		delete(c.Entries, k)
+	}
+}
+
+// RemoveEntry removes the cache entry for the defined SPN.
+func (c *Cache) RemoveEntry(spn string) {
+	c.mux.Lock()
+	defer c.mux.Unlock()
+	delete(c.Entries, spn)
+}
+
+// GetCachedTicket returns a ticket from the cache for the SPN.
+// Only a ticket that is currently valid will be returned.
+func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.EncryptionKey, bool) {
+	if e, ok := cl.cache.getEntry(spn); ok {
+		//If within time window of ticket return it
+		if time.Now().UTC().After(e.StartTime) && time.Now().UTC().Before(e.EndTime) {
+			cl.Log("ticket received from cache for %s", spn)
+			return e.Ticket, e.SessionKey, true
+		} else if time.Now().UTC().Before(e.RenewTill) {
+			e, err := cl.renewTicket(e)
+			if err != nil {
+				return e.Ticket, e.SessionKey, false
+			}
+			return e.Ticket, e.SessionKey, true
+		}
+	}
+	var tkt messages.Ticket
+	var key types.EncryptionKey
+	return tkt, key, false
+}
+
+// renewTicket renews a cache entry ticket.
+// To renew from outside the client package use GetCachedTicket
+func (cl *Client) renewTicket(e CacheEntry) (CacheEntry, error) {
+	spn := e.Ticket.SName
+	_, _, err := cl.TGSREQGenerateAndExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true)
+	if err != nil {
+		return e, err
+	}
+	e, ok := cl.cache.getEntry(e.Ticket.SName.PrincipalNameString())
+	if !ok {
+		return e, errors.New("ticket was not added to cache")
+	}
+	cl.Log("ticket renewed for %s (EndTime: %v)", spn.PrincipalNameString(), e.EndTime)
+	return e, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/client.go b/vendor/github.com/jcmturner/gokrb5/v8/client/client.go
new file mode 100644
index 0000000..074e3f1
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/client.go
@@ -0,0 +1,329 @@
+// Package client provides a client library and methods for Kerberos 5 authentication.
+package client
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"strings"
+	"time"
+
+	"github.com/jcmturner/gokrb5/v8/config"
+	"github.com/jcmturner/gokrb5/v8/credentials"
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+	"github.com/jcmturner/gokrb5/v8/iana/errorcode"
+	"github.com/jcmturner/gokrb5/v8/iana/nametype"
+	"github.com/jcmturner/gokrb5/v8/keytab"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/messages"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// Client side configuration and state.
+type Client struct {
+	Credentials *credentials.Credentials
+	Config      *config.Config
+	settings    *Settings
+	sessions    *sessions
+	cache       *Cache
+}
+
+// NewWithPassword creates a new client from a password credential.
+// Set the realm to empty string to use the default realm from config.
+func NewWithPassword(username, realm, password string, krb5conf *config.Config, settings ...func(*Settings)) *Client {
+	creds := credentials.New(username, realm)
+	return &Client{
+		Credentials: creds.WithPassword(password),
+		Config:      krb5conf,
+		settings:    NewSettings(settings...),
+		sessions: &sessions{
+			Entries: make(map[string]*session),
+		},
+		cache: NewCache(),
+	}
+}
+
+// NewWithKeytab creates a new client from a keytab credential.
+func NewWithKeytab(username, realm string, kt *keytab.Keytab, krb5conf *config.Config, settings ...func(*Settings)) *Client {
+	creds := credentials.New(username, realm)
+	return &Client{
+		Credentials: creds.WithKeytab(kt),
+		Config:      krb5conf,
+		settings:    NewSettings(settings...),
+		sessions: &sessions{
+			Entries: make(map[string]*session),
+		},
+		cache: NewCache(),
+	}
+}
+
+// NewFromCCache create a client from a populated client cache.
+//
+// WARNING: A client created from CCache does not automatically renew TGTs and a failure will occur after the TGT expires.
+func NewFromCCache(c *credentials.CCache, krb5conf *config.Config, settings ...func(*Settings)) (*Client, error) {
+	cl := &Client{
+		Credentials: c.GetClientCredentials(),
+		Config:      krb5conf,
+		settings:    NewSettings(settings...),
+		sessions: &sessions{
+			Entries: make(map[string]*session),
+		},
+		cache: NewCache(),
+	}
+	spn := types.PrincipalName{
+		NameType:   nametype.KRB_NT_SRV_INST,
+		NameString: []string{"krbtgt", c.DefaultPrincipal.Realm},
+	}
+	cred, ok := c.GetEntry(spn)
+	if !ok {
+		return cl, errors.New("TGT not found in CCache")
+	}
+	var tgt messages.Ticket
+	err := tgt.Unmarshal(cred.Ticket)
+	if err != nil {
+		return cl, fmt.Errorf("TGT bytes in cache are not valid: %v", err)
+	}
+	cl.sessions.Entries[c.DefaultPrincipal.Realm] = &session{
+		realm:      c.DefaultPrincipal.Realm,
+		authTime:   cred.AuthTime,
+		endTime:    cred.EndTime,
+		renewTill:  cred.RenewTill,
+		tgt:        tgt,
+		sessionKey: cred.Key,
+	}
+	for _, cred := range c.GetEntries() {
+		var tkt messages.Ticket
+		err = tkt.Unmarshal(cred.Ticket)
+		if err != nil {
+			return cl, fmt.Errorf("cache entry ticket bytes are not valid: %v", err)
+		}
+		cl.cache.addEntry(
+			tkt,
+			cred.AuthTime,
+			cred.StartTime,
+			cred.EndTime,
+			cred.RenewTill,
+			cred.Key,
+		)
+	}
+	return cl, nil
+}
+
+// Key returns the client's encryption key for the specified encryption type and its kvno (kvno of zero will find latest).
+// The key can be retrieved either from the keytab or generated from the client's password.
+// If the client has both a keytab and a password defined the keytab is favoured as the source for the key
+// A KRBError can be passed in the event the KDC returns one of type KDC_ERR_PREAUTH_REQUIRED and is required to derive
+// the key for pre-authentication from the client's password. If a KRBError is not available, pass nil to this argument.
+func (cl *Client) Key(etype etype.EType, kvno int, krberr *messages.KRBError) (types.EncryptionKey, int, error) {
+	if cl.Credentials.HasKeytab() && etype != nil {
+		return cl.Credentials.Keytab().GetEncryptionKey(cl.Credentials.CName(), cl.Credentials.Domain(), kvno, etype.GetETypeID())
+	} else if cl.Credentials.HasPassword() {
+		if krberr != nil && krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
+			var pas types.PADataSequence
+			err := pas.Unmarshal(krberr.EData)
+			if err != nil {
+				return types.EncryptionKey{}, 0, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err)
+			}
+			key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), krberr.CName, krberr.CRealm, etype.GetETypeID(), pas)
+			return key, 0, err
+		}
+		key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID(), types.PADataSequence{})
+		return key, 0, err
+	}
+	return types.EncryptionKey{}, 0, errors.New("credential has neither keytab or password to generate key")
+}
+
+// IsConfigured indicates if the client has the values required set.
+func (cl *Client) IsConfigured() (bool, error) {
+	if cl.Credentials.UserName() == "" {
+		return false, errors.New("client does not have a username")
+	}
+	if cl.Credentials.Domain() == "" {
+		return false, errors.New("client does not have a define realm")
+	}
+	// Client needs to have either a password, keytab or a session already (later when loading from CCache)
+	if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
+		authTime, _, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
+		if err != nil || authTime.IsZero() {
+			return false, errors.New("client has neither a keytab nor a password set and no session")
+		}
+	}
+	if !cl.Config.LibDefaults.DNSLookupKDC {
+		for _, r := range cl.Config.Realms {
+			if r.Realm == cl.Credentials.Domain() {
+				if len(r.KDC) > 0 {
+					return true, nil
+				}
+				return false, errors.New("client krb5 config does not have any defined KDCs for the default realm")
+			}
+		}
+	}
+	return true, nil
+}
+
+// Login the client with the KDC via an AS exchange.
+func (cl *Client) Login() error {
+	if ok, err := cl.IsConfigured(); !ok {
+		return err
+	}
+	if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
+		_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
+		if err != nil {
+			return krberror.Errorf(err, krberror.KRBMsgError, "no user credentials available and error getting any existing session")
+		}
+		if time.Now().UTC().After(endTime) {
+			return krberror.New(krberror.KRBMsgError, "cannot login, no user credentials available and no valid existing session")
+		}
+		// no credentials but there is a session with tgt already
+		return nil
+	}
+	ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
+	if err != nil {
+		return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AS_REQ")
+	}
+	ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
+	if err != nil {
+		return err
+	}
+	cl.addSession(ASRep.Ticket, ASRep.DecryptedEncPart)
+	return nil
+}
+
+// AffirmLogin will only perform an AS exchange with the KDC if the client does not already have a TGT.
+func (cl *Client) AffirmLogin() error {
+	_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
+	if err != nil || time.Now().UTC().After(endTime) {
+		err := cl.Login()
+		if err != nil {
+			return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
+		}
+	}
+	return nil
+}
+
+// realmLogin obtains or renews a TGT and establishes a session for the realm specified.
+func (cl *Client) realmLogin(realm string) error {
+	if realm == cl.Credentials.Domain() {
+		return cl.Login()
+	}
+	_, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
+	if err != nil || time.Now().UTC().After(endTime) {
+		err := cl.Login()
+		if err != nil {
+			return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
+		}
+	}
+	tgt, skey, err := cl.sessionTGT(cl.Credentials.Domain())
+	if err != nil {
+		return err
+	}
+
+	spn := types.PrincipalName{
+		NameType:   nametype.KRB_NT_SRV_INST,
+		NameString: []string{"krbtgt", realm},
+	}
+
+	_, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, false)
+	if err != nil {
+		return err
+	}
+	cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
+
+	return nil
+}
+
+// Destroy stops the auto-renewal of all sessions and removes the sessions and cache entries from the client.
+func (cl *Client) Destroy() {
+	creds := credentials.New("", "")
+	cl.sessions.destroy()
+	cl.cache.clear()
+	cl.Credentials = creds
+	cl.Log("client destroyed")
+}
+
+// Diagnostics runs a set of checks that the client is properly configured and writes details to the io.Writer provided.
+func (cl *Client) Diagnostics(w io.Writer) error {
+	cl.Print(w)
+	var errs []string
+	if cl.Credentials.HasKeytab() {
+		var loginRealmEncTypes []int32
+		for _, e := range cl.Credentials.Keytab().Entries {
+			if e.Principal.Realm == cl.Credentials.Realm() {
+				loginRealmEncTypes = append(loginRealmEncTypes, e.Key.KeyType)
+			}
+		}
+		for _, et := range cl.Config.LibDefaults.DefaultTktEnctypeIDs {
+			var etInKt bool
+			for _, val := range loginRealmEncTypes {
+				if val == et {
+					etInKt = true
+					break
+				}
+			}
+			if !etInKt {
+				errs = append(errs, fmt.Sprintf("default_tkt_enctypes specifies %d but this enctype is not available in the client's keytab", et))
+			}
+		}
+		for _, et := range cl.Config.LibDefaults.PreferredPreauthTypes {
+			var etInKt bool
+			for _, val := range loginRealmEncTypes {
+				if int(val) == et {
+					etInKt = true
+					break
+				}
+			}
+			if !etInKt {
+				errs = append(errs, fmt.Sprintf("preferred_preauth_types specifies %d but this enctype is not available in the client's keytab", et))
+			}
+		}
+	}
+	udpCnt, udpKDC, err := cl.Config.GetKDCs(cl.Credentials.Realm(), false)
+	if err != nil {
+		errs = append(errs, fmt.Sprintf("error when resolving KDCs for UDP communication: %v", err))
+	}
+	if udpCnt < 1 {
+		errs = append(errs, "no KDCs resolved for communication via UDP.")
+	} else {
+		b, _ := json.MarshalIndent(&udpKDC, "", "  ")
+		fmt.Fprintf(w, "UDP KDCs: %s\n", string(b))
+	}
+	tcpCnt, tcpKDC, err := cl.Config.GetKDCs(cl.Credentials.Realm(), false)
+	if err != nil {
+		errs = append(errs, fmt.Sprintf("error when resolving KDCs for TCP communication: %v", err))
+	}
+	if tcpCnt < 1 {
+		errs = append(errs, "no KDCs resolved for communication via TCP.")
+	} else {
+		b, _ := json.MarshalIndent(&tcpKDC, "", "  ")
+		fmt.Fprintf(w, "TCP KDCs: %s\n", string(b))
+	}
+
+	if errs == nil || len(errs) < 1 {
+		return nil
+	}
+	err = fmt.Errorf(strings.Join(errs, "\n"))
+	return err
+}
+
+// Print writes the details of the client to the io.Writer provided.
+func (cl *Client) Print(w io.Writer) {
+	c, _ := cl.Credentials.JSON()
+	fmt.Fprintf(w, "Credentials:\n%s\n", c)
+
+	s, _ := cl.sessions.JSON()
+	fmt.Fprintf(w, "TGT Sessions:\n%s\n", s)
+
+	c, _ = cl.cache.JSON()
+	fmt.Fprintf(w, "Service ticket cache:\n%s\n", c)
+
+	s, _ = cl.settings.JSON()
+	fmt.Fprintf(w, "Settings:\n%s\n", s)
+
+	j, _ := cl.Config.JSON()
+	fmt.Fprintf(w, "Krb5 config:\n%s\n", j)
+
+	k, _ := cl.Credentials.Keytab().JSON()
+	fmt.Fprintf(w, "Keytab:\n%s\n", k)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/network.go b/vendor/github.com/jcmturner/gokrb5/v8/client/network.go
new file mode 100644
index 0000000..634f015
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/network.go
@@ -0,0 +1,218 @@
+package client
+
+import (
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"strings"
+	"time"
+
+	"github.com/jcmturner/gokrb5/v8/iana/errorcode"
+	"github.com/jcmturner/gokrb5/v8/messages"
+)
+
+// 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.sendKDCTCP(realm, b)
+		if errtcp != nil {
+			if e, ok := errtcp.(messages.KRBError); ok {
+				return rb, e
+			}
+			return rb, fmt.Errorf("communication error with KDC via TCP: %v", errtcp)
+		}
+		return rb, nil
+	}
+	if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
+		//Try UDP first, TCP second
+		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
+				// If this is not a KRB_ERR_RESPONSE_TOO_BIG we will return immediately otherwise will try TCP.
+				return rb, e
+			}
+			// Try TCP
+			r, errtcp := cl.sendKDCTCP(realm, b)
+			if errtcp != nil {
+				if e, ok := errtcp.(messages.KRBError); ok {
+					// Got a KRBError
+					return r, e
+				}
+				return r, fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", errudp, errtcp)
+			}
+			rb = r
+		}
+		return rb, nil
+	}
+	//Try TCP first, UDP second
+	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.sendKDCUDP(realm, b)
+		if errudp != nil {
+			if e, ok := errudp.(messages.KRBError); ok {
+				// Got a KRBError
+				return rb, e
+			}
+			return rb, fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", errtcp, errudp)
+		}
+	}
+	return rb, nil
+}
+
+// sendKDCUDP sends bytes to the KDC via UDP.
+func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) {
+	var r []byte
+	_, kdcs, err := cl.Config.GetKDCs(realm, false)
+	if err != nil {
+		return r, err
+	}
+	r, err = dialSendUDP(kdcs, b)
+	if err != nil {
+		return r, err
+	}
+	return checkForKRBError(r)
+}
+
+// dialSendUDP establishes a UDP connection to a KDC.
+func dialSendUDP(kdcs map[int]string, b []byte) ([]byte, error) {
+	var errs []string
+	for i := 1; i <= len(kdcs); i++ {
+		udpAddr, err := net.ResolveUDPAddr("udp", kdcs[i])
+		if err != nil {
+			errs = append(errs, fmt.Sprintf("error resolving KDC address: %v", err))
+			continue
+		}
+
+		conn, err := net.DialTimeout("udp", udpAddr.String(), 5*time.Second)
+		if err != nil {
+			errs = append(errs, fmt.Sprintf("error setting dial timeout on connection to %s: %v", kdcs[i], err))
+			continue
+		}
+		if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
+			errs = append(errs, fmt.Sprintf("error setting deadline on connection to %s: %v", kdcs[i], err))
+			continue
+		}
+		// conn is guaranteed to be a UDPConn
+		rb, err := sendUDP(conn.(*net.UDPConn), b)
+		if err != nil {
+			errs = append(errs, fmt.Sprintf("error sneding to %s: %v", kdcs[i], err))
+			continue
+		}
+		return rb, nil
+	}
+	return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))
+}
+
+// sendUDP sends bytes to connection over UDP.
+func sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
+	var r []byte
+	defer conn.Close()
+	_, err := conn.Write(b)
+	if err != nil {
+		return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err)
+	}
+	udpbuf := make([]byte, 4096)
+	n, _, err := conn.ReadFrom(udpbuf)
+	r = udpbuf[:n]
+	if err != nil {
+		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 %s", conn.RemoteAddr().String())
+	}
+	return r, nil
+}
+
+// sendKDCTCP sends bytes to the KDC via TCP.
+func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) {
+	var r []byte
+	_, kdcs, err := cl.Config.GetKDCs(realm, true)
+	if err != nil {
+		return r, err
+	}
+	r, err = dialSendTCP(kdcs, b)
+	if err != nil {
+		return r, err
+	}
+	return checkForKRBError(r)
+}
+
+// dialKDCTCP establishes a TCP connection to a KDC.
+func dialSendTCP(kdcs map[int]string, b []byte) ([]byte, error) {
+	var errs []string
+	for i := 1; i <= len(kdcs); i++ {
+		tcpAddr, err := net.ResolveTCPAddr("tcp", kdcs[i])
+		if err != nil {
+			errs = append(errs, fmt.Sprintf("error resolving KDC address: %v", err))
+			continue
+		}
+
+		conn, err := net.DialTimeout("tcp", tcpAddr.String(), 5*time.Second)
+		if err != nil {
+			errs = append(errs, fmt.Sprintf("error setting dial timeout on connection to %s: %v", kdcs[i], err))
+			continue
+		}
+		if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
+			errs = append(errs, fmt.Sprintf("error setting deadline on connection to %s: %v", kdcs[i], err))
+			continue
+		}
+		// conn is guaranteed to be a TCPConn
+		rb, err := sendTCP(conn.(*net.TCPConn), b)
+		if err != nil {
+			errs = append(errs, fmt.Sprintf("error sneding to %s: %v", kdcs[i], err))
+			continue
+		}
+		return rb, nil
+	}
+	return nil, errors.New("error in getting a TCP connection to any of the KDCs")
+}
+
+// sendTCP sends bytes to connection over TCP.
+func sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
+	defer conn.Close()
+	var r []byte
+	// RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order.
+	hb := make([]byte, 4, 4)
+	binary.BigEndian.PutUint32(hb, uint32(len(b)))
+	b = append(hb, b...)
+
+	_, err := conn.Write(b)
+	if err != nil {
+		return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
+	}
+
+	sh := make([]byte, 4, 4)
+	_, err = conn.Read(sh)
+	if err != nil {
+		return r, fmt.Errorf("error reading response size header: %v", err)
+	}
+	s := binary.BigEndian.Uint32(sh)
+
+	rb := make([]byte, s, s)
+	_, err = io.ReadFull(conn, rb)
+	if err != nil {
+		return r, fmt.Errorf("error reading response: %v", err)
+	}
+	if len(rb) < 1 {
+		return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
+	}
+	return rb, nil
+}
+
+// checkForKRBError checks if the response bytes from the KDC are a KRBError.
+func checkForKRBError(b []byte) ([]byte, error) {
+	var KRBErr messages.KRBError
+	if err := KRBErr.Unmarshal(b); err == nil {
+		return b, KRBErr
+	}
+	return b, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/passwd.go b/vendor/github.com/jcmturner/gokrb5/v8/client/passwd.go
new file mode 100644
index 0000000..fe20559
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/passwd.go
@@ -0,0 +1,75 @@
+package client
+
+import (
+	"fmt"
+
+	"github.com/jcmturner/gokrb5/v8/kadmin"
+	"github.com/jcmturner/gokrb5/v8/messages"
+)
+
+// Kpasswd server response codes.
+const (
+	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
+)
+
+// ChangePasswd changes the password of the client to the value provided.
+func (cl *Client) ChangePasswd(newPasswd string) (bool, error) {
+	ASReq, err := messages.NewASReqForChgPasswd(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
+	if err != nil {
+		return false, err
+	}
+	ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
+	if err != nil {
+		return false, err
+	}
+
+	msg, key, err := kadmin.ChangePasswdMsg(cl.Credentials.CName(), cl.Credentials.Domain(), newPasswd, ASRep.Ticket, ASRep.DecryptedEncPart.Key)
+	if err != nil {
+		return false, err
+	}
+	r, err := cl.sendToKPasswd(msg)
+	if err != nil {
+		return false, err
+	}
+	err = r.Decrypt(key)
+	if err != nil {
+		return false, err
+	}
+	if r.ResultCode != KRB5_KPASSWD_SUCCESS {
+		return false, fmt.Errorf("error response from kadmin: code: %d; result: %s; krberror: %v", r.ResultCode, r.Result, r.KRBError)
+	}
+	cl.Credentials.WithPassword(newPasswd)
+	return true, nil
+}
+
+func (cl *Client) sendToKPasswd(msg kadmin.Request) (r kadmin.Reply, err error) {
+	_, kps, err := cl.Config.GetKpasswdServers(cl.Credentials.Domain(), true)
+	if err != nil {
+		return
+	}
+	b, err := msg.Marshal()
+	if err != nil {
+		return
+	}
+	var rb []byte
+	if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
+		rb, err = dialSendUDP(kps, b)
+		if err != nil {
+			return
+		}
+	} else {
+		rb, err = dialSendTCP(kps, b)
+		if err != nil {
+			return
+		}
+	}
+	err = r.Unmarshal(rb)
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/session.go b/vendor/github.com/jcmturner/gokrb5/v8/client/session.go
new file mode 100644
index 0000000..f7654d0
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/session.go
@@ -0,0 +1,295 @@
+package client
+
+import (
+	"encoding/json"
+	"fmt"
+	"sort"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/jcmturner/gokrb5/v8/iana/nametype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/messages"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// sessions hold TGTs and are keyed on the realm name
+type sessions struct {
+	Entries map[string]*session
+	mux     sync.RWMutex
+}
+
+// destroy erases all sessions
+func (s *sessions) destroy() {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	for k, e := range s.Entries {
+		e.destroy()
+		delete(s.Entries, k)
+	}
+}
+
+// update replaces a session with the one provided or adds it as a new one
+func (s *sessions) update(sess *session) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	// if a session already exists for this, cancel its auto renew.
+	if i, ok := s.Entries[sess.realm]; ok {
+		if i != sess {
+			// Session in the sessions cache is not the same as one provided.
+			// Cancel the one in the cache and add this one.
+			i.mux.Lock()
+			defer i.mux.Unlock()
+			i.cancel <- true
+			s.Entries[sess.realm] = sess
+			return
+		}
+	}
+	// No session for this realm was found so just add it
+	s.Entries[sess.realm] = sess
+}
+
+// get returns the session for the realm specified
+func (s *sessions) get(realm string) (*session, bool) {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	sess, ok := s.Entries[realm]
+	return sess, ok
+}
+
+// session holds the TGT details for a realm
+type session struct {
+	realm                string
+	authTime             time.Time
+	endTime              time.Time
+	renewTill            time.Time
+	tgt                  messages.Ticket
+	sessionKey           types.EncryptionKey
+	sessionKeyExpiration time.Time
+	cancel               chan bool
+	mux                  sync.RWMutex
+}
+
+// jsonSession is used to enable marshaling some information of a session in a JSON format
+type jsonSession struct {
+	Realm                string
+	AuthTime             time.Time
+	EndTime              time.Time
+	RenewTill            time.Time
+	SessionKeyExpiration time.Time
+}
+
+// AddSession adds a session for a realm with a TGT to the client's session cache.
+// A goroutine is started to automatically renew the TGT before expiry.
+func (cl *Client) addSession(tgt messages.Ticket, dep messages.EncKDCRepPart) {
+	if strings.ToLower(tgt.SName.NameString[0]) != "krbtgt" {
+		// Not a TGT
+		return
+	}
+	realm := tgt.SName.NameString[len(tgt.SName.NameString)-1]
+	s := &session{
+		realm:                realm,
+		authTime:             dep.AuthTime,
+		endTime:              dep.EndTime,
+		renewTill:            dep.RenewTill,
+		tgt:                  tgt,
+		sessionKey:           dep.Key,
+		sessionKeyExpiration: dep.KeyExpiration,
+	}
+	cl.sessions.update(s)
+	cl.enableAutoSessionRenewal(s)
+	cl.Log("TGT session added for %s (EndTime: %v)", realm, dep.EndTime)
+}
+
+// update overwrites the session details with those from the TGT and decrypted encPart
+func (s *session) update(tgt messages.Ticket, dep messages.EncKDCRepPart) {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	s.authTime = dep.AuthTime
+	s.endTime = dep.EndTime
+	s.renewTill = dep.RenewTill
+	s.tgt = tgt
+	s.sessionKey = dep.Key
+	s.sessionKeyExpiration = dep.KeyExpiration
+}
+
+// destroy will cancel any auto renewal of the session and set the expiration times to the current time
+func (s *session) destroy() {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+	if s.cancel != nil {
+		s.cancel <- true
+	}
+	s.endTime = time.Now().UTC()
+	s.renewTill = s.endTime
+	s.sessionKeyExpiration = s.endTime
+}
+
+// valid informs if the TGT is still within the valid time window
+func (s *session) valid() bool {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	t := time.Now().UTC()
+	if t.Before(s.endTime) && s.authTime.Before(t) {
+		return true
+	}
+	return false
+}
+
+// tgtDetails is a thread safe way to get the session's realm, TGT and session key values
+func (s *session) tgtDetails() (string, messages.Ticket, types.EncryptionKey) {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.realm, s.tgt, s.sessionKey
+}
+
+// timeDetails is a thread safe way to get the session's validity time values
+func (s *session) timeDetails() (string, time.Time, time.Time, time.Time, time.Time) {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	return s.realm, s.authTime, s.endTime, s.renewTill, s.sessionKeyExpiration
+}
+
+// JSON return information about the held sessions in a JSON format.
+func (s *sessions) JSON() (string, error) {
+	s.mux.RLock()
+	defer s.mux.RUnlock()
+	var js []jsonSession
+	keys := make([]string, 0, len(s.Entries))
+	for k := range s.Entries {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	for _, k := range keys {
+		r, at, et, rt, kt := s.Entries[k].timeDetails()
+		j := jsonSession{
+			Realm:                r,
+			AuthTime:             at,
+			EndTime:              et,
+			RenewTill:            rt,
+			SessionKeyExpiration: kt,
+		}
+		js = append(js, j)
+	}
+	b, err := json.MarshalIndent(js, "", "  ")
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
+
+// enableAutoSessionRenewal turns on the automatic renewal for the client's TGT session.
+func (cl *Client) enableAutoSessionRenewal(s *session) {
+	var timer *time.Timer
+	s.mux.Lock()
+	s.cancel = make(chan bool, 1)
+	s.mux.Unlock()
+	go func(s *session) {
+		for {
+			s.mux.RLock()
+			w := (s.endTime.Sub(time.Now().UTC()) * 5) / 6
+			s.mux.RUnlock()
+			if w < 0 {
+				return
+			}
+			timer = time.NewTimer(w)
+			select {
+			case <-timer.C:
+				renewal, err := cl.refreshSession(s)
+				if err != nil {
+					cl.Log("error refreshing session: %v", err)
+				}
+				if !renewal && err == nil {
+					// end this goroutine as there will have been a new login and new auto renewal goroutine created.
+					return
+				}
+			case <-s.cancel:
+				// cancel has been called. Stop the timer and exit.
+				timer.Stop()
+				return
+			}
+		}
+	}(s)
+}
+
+// renewTGT renews the client's TGT session.
+func (cl *Client) renewTGT(s *session) error {
+	realm, tgt, skey := s.tgtDetails()
+	spn := types.PrincipalName{
+		NameType:   nametype.KRB_NT_SRV_INST,
+		NameString: []string{"krbtgt", realm},
+	}
+	_, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, true)
+	if err != nil {
+		return krberror.Errorf(err, krberror.KRBMsgError, "error renewing TGT for %s", realm)
+	}
+	s.update(tgsRep.Ticket, tgsRep.DecryptedEncPart)
+	cl.sessions.update(s)
+	cl.Log("TGT session renewed for %s (EndTime: %v)", realm, tgsRep.DecryptedEncPart.EndTime)
+	return nil
+}
+
+// refreshSession updates either through renewal or creating a new login.
+// The boolean indicates if the update was a renewal.
+func (cl *Client) refreshSession(s *session) (bool, error) {
+	s.mux.RLock()
+	realm := s.realm
+	renewTill := s.renewTill
+	s.mux.RUnlock()
+	cl.Log("refreshing TGT session for %s", realm)
+	if time.Now().UTC().Before(renewTill) {
+		err := cl.renewTGT(s)
+		return true, err
+	}
+	err := cl.realmLogin(realm)
+	return false, err
+}
+
+// ensureValidSession makes sure there is a valid session for the realm
+func (cl *Client) ensureValidSession(realm string) error {
+	s, ok := cl.sessions.get(realm)
+	if ok {
+		s.mux.RLock()
+		d := s.endTime.Sub(s.authTime) / 6
+		if s.endTime.Sub(time.Now().UTC()) > d {
+			s.mux.RUnlock()
+			return nil
+		}
+		s.mux.RUnlock()
+		_, err := cl.refreshSession(s)
+		return err
+	}
+	return cl.realmLogin(realm)
+}
+
+// sessionTGTDetails is a thread safe way to get the TGT and session key values for a realm
+func (cl *Client) sessionTGT(realm string) (tgt messages.Ticket, sessionKey types.EncryptionKey, err error) {
+	err = cl.ensureValidSession(realm)
+	if err != nil {
+		return
+	}
+	s, ok := cl.sessions.get(realm)
+	if !ok {
+		err = fmt.Errorf("could not find TGT session for %s", realm)
+		return
+	}
+	_, tgt, sessionKey = s.tgtDetails()
+	return
+}
+
+// sessionTimes provides the timing information with regards to a session for the realm specified.
+func (cl *Client) sessionTimes(realm string) (authTime, endTime, renewTime, sessionExp time.Time, err error) {
+	s, ok := cl.sessions.get(realm)
+	if !ok {
+		err = fmt.Errorf("could not find TGT session for %s", realm)
+		return
+	}
+	_, authTime, endTime, renewTime, sessionExp = s.timeDetails()
+	return
+}
+
+// spnRealm resolves the realm name of a service principal name
+func (cl *Client) spnRealm(spn types.PrincipalName) string {
+	return cl.Config.ResolveRealm(spn.NameString[len(spn.NameString)-1])
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/settings.go b/vendor/github.com/jcmturner/gokrb5/v8/client/settings.go
new file mode 100644
index 0000000..bcd3945
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/settings.go
@@ -0,0 +1,93 @@
+package client
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+)
+
+// Settings holds optional client settings.
+type Settings struct {
+	disablePAFXFast         bool
+	assumePreAuthentication bool
+	preAuthEType            int32
+	logger                  *log.Logger
+}
+
+// jsonSettings is used when marshaling the Settings details to JSON format.
+type jsonSettings struct {
+	DisablePAFXFast         bool
+	AssumePreAuthentication bool
+}
+
+// NewSettings creates a new client settings struct.
+func NewSettings(settings ...func(*Settings)) *Settings {
+	s := new(Settings)
+	for _, set := range settings {
+		set(s)
+	}
+	return s
+}
+
+// DisablePAFXFAST used to configure the client to not use PA_FX_FAST.
+//
+// s := NewSettings(DisablePAFXFAST(true))
+func DisablePAFXFAST(b bool) func(*Settings) {
+	return func(s *Settings) {
+		s.disablePAFXFast = b
+	}
+}
+
+// DisablePAFXFAST indicates is the client should disable the use of PA_FX_FAST.
+func (s *Settings) DisablePAFXFAST() bool {
+	return s.disablePAFXFast
+}
+
+// AssumePreAuthentication used to configure the client to assume pre-authentication is required.
+//
+// s := NewSettings(AssumePreAuthentication(true))
+func AssumePreAuthentication(b bool) func(*Settings) {
+	return func(s *Settings) {
+		s.assumePreAuthentication = b
+	}
+}
+
+// AssumePreAuthentication indicates if the client should proactively assume using pre-authentication.
+func (s *Settings) AssumePreAuthentication() bool {
+	return s.assumePreAuthentication
+}
+
+// Logger used to configure client with a logger.
+//
+// s := NewSettings(kt, Logger(l))
+func Logger(l *log.Logger) func(*Settings) {
+	return func(s *Settings) {
+		s.logger = l
+	}
+}
+
+// Logger returns the client logger instance.
+func (s *Settings) Logger() *log.Logger {
+	return s.logger
+}
+
+// Log will write to the service's logger if it is configured.
+func (cl *Client) Log(format string, v ...interface{}) {
+	if cl.settings.Logger() != nil {
+		cl.settings.Logger().Output(2, fmt.Sprintf(format, v...))
+	}
+}
+
+// JSON returns a JSON representation of the settings.
+func (s *Settings) JSON() (string, error) {
+	js := jsonSettings{
+		DisablePAFXFast:         s.disablePAFXFast,
+		AssumePreAuthentication: s.assumePreAuthentication,
+	}
+	b, err := json.MarshalIndent(js, "", "  ")
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/config/error.go b/vendor/github.com/jcmturner/gokrb5/v8/config/error.go
new file mode 100644
index 0000000..1fbda51
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/config/error.go
@@ -0,0 +1,30 @@
+package config
+
+import "fmt"
+
+// UnsupportedDirective error.
+type UnsupportedDirective struct {
+	text string
+}
+
+// Error implements the error interface for unsupported directives.
+func (e UnsupportedDirective) Error() string {
+	return e.text
+}
+
+// Invalid config error.
+type Invalid struct {
+	text string
+}
+
+// Error implements the error interface for invalid config error.
+func (e Invalid) Error() string {
+	return e.text
+}
+
+// InvalidErrorf creates a new Invalid error.
+func InvalidErrorf(format string, a ...interface{}) Invalid {
+	return Invalid{
+		text: fmt.Sprintf("invalid krb5 config "+format, a...),
+	}
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/config/hosts.go b/vendor/github.com/jcmturner/gokrb5/v8/config/hosts.go
new file mode 100644
index 0000000..3f22c70
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/config/hosts.go
@@ -0,0 +1,141 @@
+package config
+
+import (
+	"fmt"
+	"math/rand"
+	"net"
+	"strconv"
+	"strings"
+
+	"github.com/jcmturner/dnsutils/v2"
+)
+
+// 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) {
+	if realm == "" {
+		realm = c.LibDefaults.DefaultRealm
+	}
+	kdcs := make(map[int]string)
+	var count int
+
+	// Get the KDCs from the krb5.conf.
+	var ks []string
+	for _, r := range c.Realms {
+		if r.Realm != realm {
+			continue
+		}
+		ks = r.KDC
+	}
+	count = len(ks)
+
+	if count > 0 {
+		// Order the kdcs randomly for preference.
+		kdcs = randServOrder(ks)
+		return count, kdcs, nil
+	}
+
+	if !c.LibDefaults.DNSLookupKDC {
+		return count, kdcs, fmt.Errorf("no KDCs defined in configuration for realm %s", realm)
+	}
+
+	// Use DNS to resolve kerberos SRV records.
+	proto := "udp"
+	if tcp {
+		proto = "tcp"
+	}
+	index, 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 = index
+	for k, v := range addrs {
+		kdcs[k] = strings.TrimRight(v.Target, ".") + ":" + strconv.Itoa(int(v.Port))
+	}
+	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 err != nil {
+				return count, kdcs, err
+			}
+		}
+		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
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/config/krb5conf.go b/vendor/github.com/jcmturner/gokrb5/v8/config/krb5conf.go
new file mode 100644
index 0000000..a763843
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/config/krb5conf.go
@@ -0,0 +1,728 @@
+// Package config implements KRB5 client and service configuration as described at https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html
+package config
+
+import (
+	"bufio"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"os/user"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+)
+
+// Config represents the KRB5 configuration.
+type Config struct {
+	LibDefaults LibDefaults
+	Realms      []Realm
+	DomainRealm DomainRealm
+	//CaPaths
+	//AppDefaults
+	//Plugins
+}
+
+// WeakETypeList is a list of encryption types that have been deemed weak.
+const WeakETypeList = "des-cbc-crc des-cbc-md4 des-cbc-md5 des-cbc-raw des3-cbc-raw des-hmac-sha1 arcfour-hmac-exp rc4-hmac-exp arcfour-hmac-md5-exp des"
+
+// New creates a new config struct instance.
+func New() *Config {
+	d := make(DomainRealm)
+	return &Config{
+		LibDefaults: newLibDefaults(),
+		DomainRealm: d,
+	}
+}
+
+// LibDefaults represents the [libdefaults] section of the configuration.
+type LibDefaults struct {
+	AllowWeakCrypto bool //default false
+	// ap_req_checksum_type int //unlikely to support this
+	Canonicalize bool          //default false
+	CCacheType   int           //default is 4. unlikely to implement older
+	Clockskew    time.Duration //max allowed skew in seconds, default 300
+	//Default_ccache_name string // default /tmp/krb5cc_%{uid} //Not implementing as will hold in memory
+	DefaultClientKeytabName string //default /usr/local/var/krb5/user/%{euid}/client.keytab
+	DefaultKeytabName       string //default /etc/krb5.keytab
+	DefaultRealm            string
+	DefaultTGSEnctypes      []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+	DefaultTktEnctypes      []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+	DefaultTGSEnctypeIDs    []int32  //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+	DefaultTktEnctypeIDs    []int32  //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+	DNSCanonicalizeHostname bool     //default true
+	DNSLookupKDC            bool     //default false
+	DNSLookupRealm          bool
+	ExtraAddresses          []net.IP       //Not implementing yet
+	Forwardable             bool           //default false
+	IgnoreAcceptorHostname  bool           //default false
+	K5LoginAuthoritative    bool           //default false
+	K5LoginDirectory        string         //default user's home directory. Must be owned by the user or root
+	KDCDefaultOptions       asn1.BitString //default 0x00000010 (KDC_OPT_RENEWABLE_OK)
+	KDCTimeSync             int            //default 1
+	//kdc_req_checksum_type int //unlikely to implement as for very old KDCs
+	NoAddresses         bool     //default true
+	PermittedEnctypes   []string //default aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 des3-cbc-sha1 arcfour-hmac-md5 camellia256-cts-cmac camellia128-cts-cmac des-cbc-crc des-cbc-md5 des-cbc-md4
+	PermittedEnctypeIDs []int32
+	//plugin_base_dir string //not supporting plugins
+	PreferredPreauthTypes []int         //default “17, 16, 15, 14”, which forces libkrb5 to attempt to use PKINIT if it is supported
+	Proxiable             bool          //default false
+	RDNS                  bool          //default true
+	RealmTryDomains       int           //default -1
+	RenewLifetime         time.Duration //default 0
+	SafeChecksumType      int           //default 8
+	TicketLifetime        time.Duration //default 1 day
+	UDPPreferenceLimit    int           // 1 means to always use tcp. MIT krb5 has a default value of 1465, and it prevents user setting more than 32700.
+	VerifyAPReqNofail     bool          //default false
+}
+
+// Create a new LibDefaults struct.
+func newLibDefaults() LibDefaults {
+	uid := "0"
+	var hdir string
+	usr, _ := user.Current()
+	if usr != nil {
+		uid = usr.Uid
+		hdir = usr.HomeDir
+	}
+	opts := asn1.BitString{}
+	opts.Bytes, _ = hex.DecodeString("00000010")
+	opts.BitLength = len(opts.Bytes) * 8
+	return LibDefaults{
+		CCacheType:              4,
+		Clockskew:               time.Duration(300) * time.Second,
+		DefaultClientKeytabName: fmt.Sprintf("/usr/local/var/krb5/user/%s/client.keytab", uid),
+		DefaultKeytabName:       "/etc/krb5.keytab",
+		DefaultTGSEnctypes:      []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
+		DefaultTktEnctypes:      []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
+		DNSCanonicalizeHostname: true,
+		K5LoginDirectory:        hdir,
+		KDCDefaultOptions:       opts,
+		KDCTimeSync:             1,
+		NoAddresses:             true,
+		PermittedEnctypes:       []string{"aes256-cts-hmac-sha1-96", "aes128-cts-hmac-sha1-96", "des3-cbc-sha1", "arcfour-hmac-md5", "camellia256-cts-cmac", "camellia128-cts-cmac", "des-cbc-crc", "des-cbc-md5", "des-cbc-md4"},
+		RDNS:                    true,
+		RealmTryDomains:         -1,
+		SafeChecksumType:        8,
+		TicketLifetime:          time.Duration(24) * time.Hour,
+		UDPPreferenceLimit:      1465,
+		PreferredPreauthTypes:   []int{17, 16, 15, 14},
+	}
+}
+
+// Parse the lines of the [libdefaults] section of the configuration into the LibDefaults struct.
+func (l *LibDefaults) parseLines(lines []string) error {
+	for _, line := range lines {
+		//Remove comments after the values
+		if idx := strings.IndexAny(line, "#;"); idx != -1 {
+			line = line[:idx]
+		}
+		line = strings.TrimSpace(line)
+		if line == "" {
+			continue
+		}
+		if !strings.Contains(line, "=") {
+			return InvalidErrorf("libdefaults section line (%s)", line)
+		}
+
+		p := strings.Split(line, "=")
+		key := strings.TrimSpace(strings.ToLower(p[0]))
+		switch key {
+		case "allow_weak_crypto":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.AllowWeakCrypto = v
+		case "canonicalize":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.Canonicalize = v
+		case "ccache_type":
+			p[1] = strings.TrimSpace(p[1])
+			v, err := strconv.ParseUint(p[1], 10, 32)
+			if err != nil || v < 0 || v > 4 {
+				return InvalidErrorf("libdefaults section line (%s)", line)
+			}
+			l.CCacheType = int(v)
+		case "clockskew":
+			d, err := parseDuration(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.Clockskew = d
+		case "default_client_keytab_name":
+			l.DefaultClientKeytabName = strings.TrimSpace(p[1])
+		case "default_keytab_name":
+			l.DefaultKeytabName = strings.TrimSpace(p[1])
+		case "default_realm":
+			l.DefaultRealm = strings.TrimSpace(p[1])
+		case "default_tgs_enctypes":
+			l.DefaultTGSEnctypes = strings.Fields(p[1])
+		case "default_tkt_enctypes":
+			l.DefaultTktEnctypes = strings.Fields(p[1])
+		case "dns_canonicalize_hostname":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.DNSCanonicalizeHostname = v
+		case "dns_lookup_kdc":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.DNSLookupKDC = v
+		case "dns_lookup_realm":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.DNSLookupRealm = v
+		case "extra_addresses":
+			ipStr := strings.TrimSpace(p[1])
+			for _, ip := range strings.Split(ipStr, ",") {
+				if eip := net.ParseIP(ip); eip != nil {
+					l.ExtraAddresses = append(l.ExtraAddresses, eip)
+				}
+			}
+		case "forwardable":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.Forwardable = v
+		case "ignore_acceptor_hostname":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.IgnoreAcceptorHostname = v
+		case "k5login_authoritative":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.K5LoginAuthoritative = v
+		case "k5login_directory":
+			l.K5LoginDirectory = strings.TrimSpace(p[1])
+		case "kdc_default_options":
+			v := strings.TrimSpace(p[1])
+			v = strings.Replace(v, "0x", "", -1)
+			b, err := hex.DecodeString(v)
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.KDCDefaultOptions.Bytes = b
+			l.KDCDefaultOptions.BitLength = len(b) * 8
+		case "kdc_timesync":
+			p[1] = strings.TrimSpace(p[1])
+			v, err := strconv.ParseInt(p[1], 10, 32)
+			if err != nil || v < 0 {
+				return InvalidErrorf("libdefaults section line (%s)", line)
+			}
+			l.KDCTimeSync = int(v)
+		case "noaddresses":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.NoAddresses = v
+		case "permitted_enctypes":
+			l.PermittedEnctypes = strings.Fields(p[1])
+		case "preferred_preauth_types":
+			p[1] = strings.TrimSpace(p[1])
+			t := strings.Split(p[1], ",")
+			var v []int
+			for _, s := range t {
+				i, err := strconv.ParseInt(s, 10, 32)
+				if err != nil {
+					return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+				}
+				v = append(v, int(i))
+			}
+			l.PreferredPreauthTypes = v
+		case "proxiable":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.Proxiable = v
+		case "rdns":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.RDNS = v
+		case "realm_try_domains":
+			p[1] = strings.TrimSpace(p[1])
+			v, err := strconv.ParseInt(p[1], 10, 32)
+			if err != nil || v < -1 {
+				return InvalidErrorf("libdefaults section line (%s)", line)
+			}
+			l.RealmTryDomains = int(v)
+		case "renew_lifetime":
+			d, err := parseDuration(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.RenewLifetime = d
+		case "safe_checksum_type":
+			p[1] = strings.TrimSpace(p[1])
+			v, err := strconv.ParseInt(p[1], 10, 32)
+			if err != nil || v < 0 {
+				return InvalidErrorf("libdefaults section line (%s)", line)
+			}
+			l.SafeChecksumType = int(v)
+		case "ticket_lifetime":
+			d, err := parseDuration(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.TicketLifetime = d
+		case "udp_preference_limit":
+			p[1] = strings.TrimSpace(p[1])
+			v, err := strconv.ParseUint(p[1], 10, 32)
+			if err != nil || v > 32700 {
+				return InvalidErrorf("libdefaults section line (%s)", line)
+			}
+			l.UDPPreferenceLimit = int(v)
+		case "verify_ap_req_nofail":
+			v, err := parseBoolean(p[1])
+			if err != nil {
+				return InvalidErrorf("libdefaults section line (%s): %v", line, err)
+			}
+			l.VerifyAPReqNofail = v
+		}
+	}
+	l.DefaultTGSEnctypeIDs = parseETypes(l.DefaultTGSEnctypes, l.AllowWeakCrypto)
+	l.DefaultTktEnctypeIDs = parseETypes(l.DefaultTktEnctypes, l.AllowWeakCrypto)
+	l.PermittedEnctypeIDs = parseETypes(l.PermittedEnctypes, l.AllowWeakCrypto)
+	return nil
+}
+
+// Realm represents an entry in the [realms] section of the configuration.
+type Realm struct {
+	Realm       string
+	AdminServer []string
+	//auth_to_local //Not implementing for now
+	//auth_to_local_names //Not implementing for now
+	DefaultDomain string
+	KDC           []string
+	KPasswdServer []string //default admin_server:464
+	MasterKDC     []string
+}
+
+// Parse the lines of a [realms] entry into the Realm struct.
+func (r *Realm) parseLines(name string, lines []string) (err error) {
+	r.Realm = name
+	var adminServerFinal bool
+	var KDCFinal bool
+	var kpasswdServerFinal bool
+	var masterKDCFinal bool
+	var ignore bool
+	var c int // counts the depth of blocks within brackets { }
+	for _, line := range lines {
+		if ignore && c > 0 && !strings.Contains(line, "{") && !strings.Contains(line, "}") {
+			continue
+		}
+		//Remove comments after the values
+		if idx := strings.IndexAny(line, "#;"); idx != -1 {
+			line = line[:idx]
+		}
+		line = strings.TrimSpace(line)
+		if line == "" {
+			continue
+		}
+		if !strings.Contains(line, "=") && !strings.Contains(line, "}") {
+			return InvalidErrorf("realms section line (%s)", line)
+		}
+		if strings.Contains(line, "v4_") {
+			ignore = true
+			err = UnsupportedDirective{"v4 configurations are not supported"}
+		}
+		if strings.Contains(line, "{") {
+			c++
+			if ignore {
+				continue
+			}
+		}
+		if strings.Contains(line, "}") {
+			c--
+			if c < 0 {
+				return InvalidErrorf("unpaired curly brackets")
+			}
+			if ignore {
+				if c < 1 {
+					c = 0
+					ignore = false
+				}
+				continue
+			}
+		}
+
+		p := strings.Split(line, "=")
+		key := strings.TrimSpace(strings.ToLower(p[0]))
+		v := strings.TrimSpace(p[1])
+		switch key {
+		case "admin_server":
+			appendUntilFinal(&r.AdminServer, v, &adminServerFinal)
+		case "default_domain":
+			r.DefaultDomain = v
+		case "kdc":
+			if !strings.Contains(v, ":") {
+				// No port number specified default to 88
+				if strings.HasSuffix(v, `*`) {
+					v = strings.TrimSpace(strings.TrimSuffix(v, `*`)) + ":88*"
+				} else {
+					v = strings.TrimSpace(v) + ":88"
+				}
+			}
+			appendUntilFinal(&r.KDC, v, &KDCFinal)
+		case "kpasswd_server":
+			appendUntilFinal(&r.KPasswdServer, v, &kpasswdServerFinal)
+		case "master_kdc":
+			appendUntilFinal(&r.MasterKDC, v, &masterKDCFinal)
+		}
+	}
+	//default for Kpasswd_server = admin_server:464
+	if len(r.KPasswdServer) < 1 {
+		for _, a := range r.AdminServer {
+			s := strings.Split(a, ":")
+			r.KPasswdServer = append(r.KPasswdServer, s[0]+":464")
+		}
+	}
+	return
+}
+
+// Parse the lines of the [realms] section of the configuration into an slice of Realm structs.
+func parseRealms(lines []string) (realms []Realm, err error) {
+	var name string
+	var start int
+	var c int
+	for i, l := range lines {
+		//Remove comments after the values
+		if idx := strings.IndexAny(l, "#;"); idx != -1 {
+			l = l[:idx]
+		}
+		l = strings.TrimSpace(l)
+		if l == "" {
+			continue
+		}
+		//if strings.Contains(l, "v4_") {
+		//	return nil, errors.New("v4 configurations are not supported in Realms section")
+		//}
+		if strings.Contains(l, "{") {
+			c++
+			if !strings.Contains(l, "=") {
+				return nil, fmt.Errorf("realm configuration line invalid: %s", l)
+			}
+			if c == 1 {
+				start = i
+				p := strings.Split(l, "=")
+				name = strings.TrimSpace(p[0])
+			}
+		}
+		if strings.Contains(l, "}") {
+			if c < 1 {
+				// but not started a block!!!
+				return nil, errors.New("invalid Realms section in configuration")
+			}
+			c--
+			if c == 0 {
+				var r Realm
+				e := r.parseLines(name, lines[start+1:i])
+				if e != nil {
+					if _, ok := e.(UnsupportedDirective); !ok {
+						err = e
+						return
+					}
+					err = e
+				}
+				realms = append(realms, r)
+			}
+		}
+	}
+	return
+}
+
+// DomainRealm maps the domains to realms representing the [domain_realm] section of the configuration.
+type DomainRealm map[string]string
+
+// Parse the lines of the [domain_realm] section of the configuration and add to the mapping.
+func (d *DomainRealm) parseLines(lines []string) error {
+	for _, line := range lines {
+		//Remove comments after the values
+		if idx := strings.IndexAny(line, "#;"); idx != -1 {
+			line = line[:idx]
+		}
+		if strings.TrimSpace(line) == "" {
+			continue
+		}
+		if !strings.Contains(line, "=") {
+			return InvalidErrorf("realm line (%s)", line)
+		}
+		p := strings.Split(line, "=")
+		domain := strings.TrimSpace(strings.ToLower(p[0]))
+		realm := strings.TrimSpace(p[1])
+		d.addMapping(domain, realm)
+	}
+	return nil
+}
+
+// Add a domain to realm mapping.
+func (d *DomainRealm) addMapping(domain, realm string) {
+	(*d)[domain] = realm
+}
+
+// Delete a domain to realm mapping.
+func (d *DomainRealm) deleteMapping(domain, realm string) {
+	delete(*d, domain)
+}
+
+// ResolveRealm resolves the kerberos realm for the specified domain name from the domain to realm mapping.
+// The most specific mapping is returned.
+func (c *Config) ResolveRealm(domainName string) string {
+	domainName = strings.TrimSuffix(domainName, ".")
+
+	// Try to match the entire hostname first
+	if r, ok := c.DomainRealm[domainName]; ok {
+		return r
+	}
+
+	// Try to match all DNS domain parts
+	periods := strings.Count(domainName, ".") + 1
+	for i := 2; i <= periods; i++ {
+		z := strings.SplitN(domainName, ".", i)
+		if r, ok := c.DomainRealm["."+z[len(z)-1]]; ok {
+			return r
+		}
+	}
+	return c.LibDefaults.DefaultRealm
+}
+
+// Load the KRB5 configuration from the specified file path.
+func Load(cfgPath string) (*Config, error) {
+	fh, err := os.Open(cfgPath)
+	if err != nil {
+		return nil, errors.New("configuration file could not be opened: " + cfgPath + " " + err.Error())
+	}
+	defer fh.Close()
+	scanner := bufio.NewScanner(fh)
+	return NewFromScanner(scanner)
+}
+
+// NewFromString creates a new Config struct from a string.
+func NewFromString(s string) (*Config, error) {
+	reader := strings.NewReader(s)
+	return NewFromReader(reader)
+}
+
+// NewFromReader creates a new Config struct from an io.Reader.
+func NewFromReader(r io.Reader) (*Config, error) {
+	scanner := bufio.NewScanner(r)
+	return NewFromScanner(scanner)
+}
+
+// NewFromScanner creates a new Config struct from a bufio.Scanner.
+func NewFromScanner(scanner *bufio.Scanner) (*Config, error) {
+	c := New()
+	var e error
+	sections := make(map[int]string)
+	var sectionLineNum []int
+	var lines []string
+	for scanner.Scan() {
+		// Skip comments and blank lines
+		if matched, _ := regexp.MatchString(`^\s*(#|;|\n)`, scanner.Text()); matched {
+			continue
+		}
+		if matched, _ := regexp.MatchString(`^\s*\[libdefaults\]\s*`, scanner.Text()); matched {
+			sections[len(lines)] = "libdefaults"
+			sectionLineNum = append(sectionLineNum, len(lines))
+			continue
+		}
+		if matched, _ := regexp.MatchString(`^\s*\[realms\]\s*`, scanner.Text()); matched {
+			sections[len(lines)] = "realms"
+			sectionLineNum = append(sectionLineNum, len(lines))
+			continue
+		}
+		if matched, _ := regexp.MatchString(`^\s*\[domain_realm\]\s*`, scanner.Text()); matched {
+			sections[len(lines)] = "domain_realm"
+			sectionLineNum = append(sectionLineNum, len(lines))
+			continue
+		}
+		if matched, _ := regexp.MatchString(`^\s*\[.*\]\s*`, scanner.Text()); matched {
+			sections[len(lines)] = "unknown_section"
+			sectionLineNum = append(sectionLineNum, len(lines))
+			continue
+		}
+		lines = append(lines, scanner.Text())
+	}
+	for i, start := range sectionLineNum {
+		var end int
+		if i+1 >= len(sectionLineNum) {
+			end = len(lines)
+		} else {
+			end = sectionLineNum[i+1]
+		}
+		switch section := sections[start]; section {
+		case "libdefaults":
+			err := c.LibDefaults.parseLines(lines[start:end])
+			if err != nil {
+				if _, ok := err.(UnsupportedDirective); !ok {
+					return nil, fmt.Errorf("error processing libdefaults section: %v", err)
+				}
+				e = err
+			}
+		case "realms":
+			realms, err := parseRealms(lines[start:end])
+			if err != nil {
+				if _, ok := err.(UnsupportedDirective); !ok {
+					return nil, fmt.Errorf("error processing realms section: %v", err)
+				}
+				e = err
+			}
+			c.Realms = realms
+		case "domain_realm":
+			err := c.DomainRealm.parseLines(lines[start:end])
+			if err != nil {
+				if _, ok := err.(UnsupportedDirective); !ok {
+					return nil, fmt.Errorf("error processing domaain_realm section: %v", err)
+				}
+				e = err
+			}
+		}
+	}
+	return c, e
+}
+
+// Parse a space delimited list of ETypes into a list of EType numbers optionally filtering out weak ETypes.
+func parseETypes(s []string, w bool) []int32 {
+	var eti []int32
+	for _, et := range s {
+		if !w {
+			var weak bool
+			for _, wet := range strings.Fields(WeakETypeList) {
+				if et == wet {
+					weak = true
+					break
+				}
+			}
+			if weak {
+				continue
+			}
+		}
+		i := etypeID.EtypeSupported(et)
+		if i != 0 {
+			eti = append(eti, i)
+		}
+	}
+	return eti
+}
+
+// Parse a time duration string in the configuration to a golang time.Duration.
+func parseDuration(s string) (time.Duration, error) {
+	s = strings.Replace(strings.TrimSpace(s), " ", "", -1)
+
+	// handle Nd[NmNs]
+	if strings.Contains(s, "d") {
+		ds := strings.SplitN(s, "d", 2)
+		dn, err := strconv.ParseUint(ds[0], 10, 32)
+		if err != nil {
+			return time.Duration(0), errors.New("invalid time duration")
+		}
+		d := time.Duration(dn*24) * time.Hour
+		if ds[1] != "" {
+			dp, err := time.ParseDuration(ds[1])
+			if err != nil {
+				return time.Duration(0), errors.New("invalid time duration")
+			}
+			d = d + dp
+		}
+		return d, nil
+	}
+
+	// handle Nm[Ns]
+	d, err := time.ParseDuration(s)
+	if err == nil {
+		return d, nil
+	}
+
+	// handle N
+	v, err := strconv.ParseUint(s, 10, 32)
+	if err == nil && v > 0 {
+		return time.Duration(v) * time.Second, nil
+	}
+
+	// handle h:m[:s]
+	if strings.Contains(s, ":") {
+		t := strings.Split(s, ":")
+		if 2 > len(t) || len(t) > 3 {
+			return time.Duration(0), errors.New("invalid time duration value")
+		}
+		var i []int
+		for _, n := range t {
+			j, err := strconv.ParseInt(n, 10, 16)
+			if err != nil {
+				return time.Duration(0), errors.New("invalid time duration value")
+			}
+			i = append(i, int(j))
+		}
+		d := time.Duration(i[0])*time.Hour + time.Duration(i[1])*time.Minute
+		if len(i) == 3 {
+			d = d + time.Duration(i[2])*time.Second
+		}
+		return d, nil
+	}
+	return time.Duration(0), errors.New("invalid time duration value")
+}
+
+// Parse possible boolean values to golang bool.
+func parseBoolean(s string) (bool, error) {
+	s = strings.TrimSpace(s)
+	v, err := strconv.ParseBool(s)
+	if err == nil {
+		return v, nil
+	}
+	switch strings.ToLower(s) {
+	case "yes":
+		return true, nil
+	case "y":
+		return true, nil
+	case "no":
+		return false, nil
+	case "n":
+		return false, nil
+	}
+	return false, errors.New("invalid boolean value")
+}
+
+// Parse array of strings but stop if an asterisk is placed at the end of a line.
+func appendUntilFinal(s *[]string, value string, final *bool) {
+	if *final {
+		return
+	}
+	if last := len(value) - 1; last >= 0 && value[last] == '*' {
+		*final = true
+		value = value[:len(value)-1]
+	}
+	*s = append(*s, value)
+}
+
+// JSON return details of the config in a JSON format.
+func (c *Config) JSON() (string, error) {
+	b, err := json.MarshalIndent(c, "", "  ")
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/credentials/ccache.go b/vendor/github.com/jcmturner/gokrb5/v8/credentials/ccache.go
new file mode 100644
index 0000000..c3b35c7
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/credentials/ccache.go
@@ -0,0 +1,333 @@
+package credentials
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"io/ioutil"
+	"strings"
+	"time"
+	"unsafe"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+const (
+	headerFieldTagKDCOffset = 1
+)
+
+// CCache is the file credentials cache as define here: https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html
+type CCache struct {
+	Version          uint8
+	Header           header
+	DefaultPrincipal principal
+	Credentials      []*Credential
+	Path             string
+}
+
+type header struct {
+	length uint16
+	fields []headerField
+}
+
+type headerField struct {
+	tag    uint16
+	length uint16
+	value  []byte
+}
+
+// Credential cache entry principal struct.
+type principal struct {
+	Realm         string
+	PrincipalName types.PrincipalName
+}
+
+// Credential holds a Kerberos client's ccache credential information.
+type Credential struct {
+	Client       principal
+	Server       principal
+	Key          types.EncryptionKey
+	AuthTime     time.Time
+	StartTime    time.Time
+	EndTime      time.Time
+	RenewTill    time.Time
+	IsSKey       bool
+	TicketFlags  asn1.BitString
+	Addresses    []types.HostAddress
+	AuthData     []types.AuthorizationDataEntry
+	Ticket       []byte
+	SecondTicket []byte
+}
+
+// LoadCCache loads a credential cache file into a CCache type.
+func LoadCCache(cpath string) (*CCache, error) {
+	c := new(CCache)
+	b, err := ioutil.ReadFile(cpath)
+	if err != nil {
+		return c, err
+	}
+	err = c.Unmarshal(b)
+	return c, err
+}
+
+// Unmarshal a byte slice of credential cache data into CCache type.
+func (c *CCache) Unmarshal(b []byte) error {
+	p := 0
+	//The first byte of the file always has the value 5
+	if int8(b[p]) != 5 {
+		return errors.New("Invalid credential cache data. First byte does not equal 5")
+	}
+	p++
+	//Get credential cache version
+	//The second byte contains the version number (1 to 4)
+	c.Version = b[p]
+	if c.Version < 1 || c.Version > 4 {
+		return errors.New("Invalid credential cache data. Keytab version is not within 1 to 4")
+	}
+	p++
+	//Version 1 or 2 of the file format uses native byte order for integer representations. Versions 3 & 4 always uses big-endian byte order
+	var endian binary.ByteOrder
+	endian = binary.BigEndian
+	if (c.Version == 1 || c.Version == 2) && isNativeEndianLittle() {
+		endian = binary.LittleEndian
+	}
+	if c.Version == 4 {
+		err := parseHeader(b, &p, c, &endian)
+		if err != nil {
+			return err
+		}
+	}
+	c.DefaultPrincipal = parsePrincipal(b, &p, c, &endian)
+	for p < len(b) {
+		cred, err := parseCredential(b, &p, c, &endian)
+		if err != nil {
+			return err
+		}
+		c.Credentials = append(c.Credentials, cred)
+	}
+	return nil
+}
+
+func parseHeader(b []byte, p *int, c *CCache, e *binary.ByteOrder) error {
+	if c.Version != 4 {
+		return errors.New("Credentials cache version is not 4 so there is no header to parse.")
+	}
+	h := header{}
+	h.length = uint16(readInt16(b, p, e))
+	for *p <= int(h.length) {
+		f := headerField{}
+		f.tag = uint16(readInt16(b, p, e))
+		f.length = uint16(readInt16(b, p, e))
+		f.value = b[*p : *p+int(f.length)]
+		*p += int(f.length)
+		if !f.valid() {
+			return errors.New("Invalid credential cache header found")
+		}
+		h.fields = append(h.fields, f)
+	}
+	c.Header = h
+	return nil
+}
+
+// Parse the Keytab bytes of a principal into a Keytab entry's principal.
+func parsePrincipal(b []byte, p *int, c *CCache, e *binary.ByteOrder) (princ principal) {
+	if c.Version != 1 {
+		//Name Type is omitted in version 1
+		princ.PrincipalName.NameType = readInt32(b, p, e)
+	}
+	nc := int(readInt32(b, p, e))
+	if c.Version == 1 {
+		//In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
+		nc--
+	}
+	lenRealm := readInt32(b, p, e)
+	princ.Realm = string(readBytes(b, p, int(lenRealm), e))
+	for i := 0; i < nc; i++ {
+		l := readInt32(b, p, e)
+		princ.PrincipalName.NameString = append(princ.PrincipalName.NameString, string(readBytes(b, p, int(l), e)))
+	}
+	return princ
+}
+
+func parseCredential(b []byte, p *int, c *CCache, e *binary.ByteOrder) (cred *Credential, err error) {
+	cred = new(Credential)
+	cred.Client = parsePrincipal(b, p, c, e)
+	cred.Server = parsePrincipal(b, p, c, e)
+	key := types.EncryptionKey{}
+	key.KeyType = int32(readInt16(b, p, e))
+	if c.Version == 3 {
+		//repeated twice in version 3
+		key.KeyType = int32(readInt16(b, p, e))
+	}
+	key.KeyValue = readData(b, p, e)
+	cred.Key = key
+	cred.AuthTime = readTimestamp(b, p, e)
+	cred.StartTime = readTimestamp(b, p, e)
+	cred.EndTime = readTimestamp(b, p, e)
+	cred.RenewTill = readTimestamp(b, p, e)
+	if ik := readInt8(b, p, e); ik == 0 {
+		cred.IsSKey = false
+	} else {
+		cred.IsSKey = true
+	}
+	cred.TicketFlags = types.NewKrbFlags()
+	cred.TicketFlags.Bytes = readBytes(b, p, 4, e)
+	l := int(readInt32(b, p, e))
+	cred.Addresses = make([]types.HostAddress, l, l)
+	for i := range cred.Addresses {
+		cred.Addresses[i] = readAddress(b, p, e)
+	}
+	l = int(readInt32(b, p, e))
+	cred.AuthData = make([]types.AuthorizationDataEntry, l, l)
+	for i := range cred.AuthData {
+		cred.AuthData[i] = readAuthDataEntry(b, p, e)
+	}
+	cred.Ticket = readData(b, p, e)
+	cred.SecondTicket = readData(b, p, e)
+	return
+}
+
+// GetClientPrincipalName returns a PrincipalName type for the client the credentials cache is for.
+func (c *CCache) GetClientPrincipalName() types.PrincipalName {
+	return c.DefaultPrincipal.PrincipalName
+}
+
+// GetClientRealm returns the reals of the client the credentials cache is for.
+func (c *CCache) GetClientRealm() string {
+	return c.DefaultPrincipal.Realm
+}
+
+// GetClientCredentials returns a Credentials object representing the client of the credentials cache.
+func (c *CCache) GetClientCredentials() *Credentials {
+	return &Credentials{
+		username: c.DefaultPrincipal.PrincipalName.PrincipalNameString(),
+		realm:    c.GetClientRealm(),
+		cname:    c.DefaultPrincipal.PrincipalName,
+	}
+}
+
+// Contains tests if the cache contains a credential for the provided server PrincipalName
+func (c *CCache) Contains(p types.PrincipalName) bool {
+	for _, cred := range c.Credentials {
+		if cred.Server.PrincipalName.Equal(p) {
+			return true
+		}
+	}
+	return false
+}
+
+// GetEntry returns a specific credential for the PrincipalName provided.
+func (c *CCache) GetEntry(p types.PrincipalName) (*Credential, bool) {
+	cred := new(Credential)
+	var found bool
+	for i := range c.Credentials {
+		if c.Credentials[i].Server.PrincipalName.Equal(p) {
+			cred = c.Credentials[i]
+			found = true
+			break
+		}
+	}
+	if !found {
+		return cred, false
+	}
+	return cred, true
+}
+
+// GetEntries filters out configuration entries an returns a slice of credentials.
+func (c *CCache) GetEntries() []*Credential {
+	creds := make([]*Credential, 0)
+	for _, cred := range c.Credentials {
+		// Filter out configuration entries
+		if strings.HasPrefix(cred.Server.Realm, "X-CACHECONF") {
+			continue
+		}
+		creds = append(creds, cred)
+	}
+	return creds
+}
+
+func (h *headerField) valid() bool {
+	// See https://web.mit.edu/kerberos/krb5-latest/doc/formats/ccache_file_format.html - Header format
+	switch h.tag {
+	case headerFieldTagKDCOffset:
+		if h.length != 8 || len(h.value) != 8 {
+			return false
+		}
+		return true
+	}
+	return false
+}
+
+func readData(b []byte, p *int, e *binary.ByteOrder) []byte {
+	l := readInt32(b, p, e)
+	return readBytes(b, p, int(l), e)
+}
+
+func readAddress(b []byte, p *int, e *binary.ByteOrder) types.HostAddress {
+	a := types.HostAddress{}
+	a.AddrType = int32(readInt16(b, p, e))
+	a.Address = readData(b, p, e)
+	return a
+}
+
+func readAuthDataEntry(b []byte, p *int, e *binary.ByteOrder) types.AuthorizationDataEntry {
+	a := types.AuthorizationDataEntry{}
+	a.ADType = int32(readInt16(b, p, e))
+	a.ADData = readData(b, p, e)
+	return a
+}
+
+// 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)
+}
+
+// Read bytes representing an eight bit integer.
+func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8) {
+	buf := bytes.NewBuffer(b[*p : *p+1])
+	binary.Read(buf, *e, &i)
+	*p++
+	return
+}
+
+// Read bytes representing a sixteen bit integer.
+func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16) {
+	buf := bytes.NewBuffer(b[*p : *p+2])
+	binary.Read(buf, *e, &i)
+	*p += 2
+	return
+}
+
+// Read bytes representing a thirty two bit integer.
+func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32) {
+	buf := bytes.NewBuffer(b[*p : *p+4])
+	binary.Read(buf, *e, &i)
+	*p += 4
+	return
+}
+
+func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) []byte {
+	buf := bytes.NewBuffer(b[*p : *p+s])
+	r := make([]byte, s)
+	binary.Read(buf, *e, &r)
+	*p += s
+	return r
+}
+
+func isNativeEndianLittle() bool {
+	var x = 0x012345678
+	var p = unsafe.Pointer(&x)
+	var bp = (*[4]byte)(p)
+
+	var endian bool
+	if 0x01 == bp[0] {
+		endian = false
+	} else if (0x78 & 0xff) == (bp[0] & 0xff) {
+		endian = true
+	} else {
+		// Default to big endian
+		endian = false
+	}
+	return endian
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/credentials/credentials.go b/vendor/github.com/jcmturner/gokrb5/v8/credentials/credentials.go
new file mode 100644
index 0000000..bddbc7e
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/credentials/credentials.go
@@ -0,0 +1,405 @@
+// Package credentials provides credentials management for Kerberos 5 authentication.
+package credentials
+
+import (
+	"bytes"
+	"encoding/gob"
+	"encoding/json"
+	"time"
+
+	"github.com/hashicorp/go-uuid"
+	"github.com/jcmturner/gokrb5/v8/iana/nametype"
+	"github.com/jcmturner/gokrb5/v8/keytab"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+const (
+	// AttributeKeyADCredentials assigned number for AD credentials.
+	AttributeKeyADCredentials = "gokrb5AttributeKeyADCredentials"
+)
+
+// Credentials struct for a user.
+// Contains either a keytab, password or both.
+// Keytabs are used over passwords if both are defined.
+type Credentials struct {
+	username        string
+	displayName     string
+	realm           string
+	cname           types.PrincipalName
+	keytab          *keytab.Keytab
+	password        string
+	attributes      map[string]interface{}
+	validUntil      time.Time
+	authenticated   bool
+	human           bool
+	authTime        time.Time
+	groupMembership map[string]bool
+	sessionID       string
+}
+
+// marshalCredentials is used to enable marshaling and unmarshaling of credentials
+// without having exported fields on the Credentials struct
+type marshalCredentials struct {
+	Username        string
+	DisplayName     string
+	Realm           string
+	CName           types.PrincipalName `json:"-"`
+	Keytab          bool
+	Password        bool
+	Attributes      map[string]interface{} `json:"-"`
+	ValidUntil      time.Time
+	Authenticated   bool
+	Human           bool
+	AuthTime        time.Time
+	GroupMembership map[string]bool `json:"-"`
+	SessionID       string
+}
+
+// ADCredentials contains information obtained from the PAC.
+type ADCredentials struct {
+	EffectiveName       string
+	FullName            string
+	UserID              int
+	PrimaryGroupID      int
+	LogOnTime           time.Time
+	LogOffTime          time.Time
+	PasswordLastSet     time.Time
+	GroupMembershipSIDs []string
+	LogonDomainName     string
+	LogonDomainID       string
+	LogonServer         string
+}
+
+// New creates a new Credentials instance.
+func New(username string, realm string) *Credentials {
+	uid, err := uuid.GenerateUUID()
+	if err != nil {
+		uid = "00unique-sess-ions-uuid-unavailable0"
+	}
+	return &Credentials{
+		username:        username,
+		displayName:     username,
+		realm:           realm,
+		cname:           types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, username),
+		keytab:          keytab.New(),
+		attributes:      make(map[string]interface{}),
+		groupMembership: make(map[string]bool),
+		sessionID:       uid,
+		human:           true,
+	}
+}
+
+// NewFromPrincipalName creates a new Credentials instance with the user details provides as a PrincipalName type.
+func NewFromPrincipalName(cname types.PrincipalName, realm string) *Credentials {
+	c := New(cname.PrincipalNameString(), realm)
+	c.cname = cname
+	return c
+}
+
+// WithKeytab sets the Keytab in the Credentials struct.
+func (c *Credentials) WithKeytab(kt *keytab.Keytab) *Credentials {
+	c.keytab = kt
+	c.password = ""
+	return c
+}
+
+// Keytab returns the credential's Keytab.
+func (c *Credentials) Keytab() *keytab.Keytab {
+	return c.keytab
+}
+
+// HasKeytab queries if the Credentials has a keytab defined.
+func (c *Credentials) HasKeytab() bool {
+	if c.keytab != nil && len(c.keytab.Entries) > 0 {
+		return true
+	}
+	return false
+}
+
+// WithPassword sets the password in the Credentials struct.
+func (c *Credentials) WithPassword(password string) *Credentials {
+	c.password = password
+	c.keytab = keytab.New() // clear any keytab
+	return c
+}
+
+// Password returns the credential's password.
+func (c *Credentials) Password() string {
+	return c.password
+}
+
+// HasPassword queries if the Credentials has a password defined.
+func (c *Credentials) HasPassword() bool {
+	if c.password != "" {
+		return true
+	}
+	return false
+}
+
+// SetValidUntil sets the expiry time of the credentials
+func (c *Credentials) SetValidUntil(t time.Time) {
+	c.validUntil = t
+}
+
+// SetADCredentials adds ADCredentials attributes to the credentials
+func (c *Credentials) SetADCredentials(a ADCredentials) {
+	c.SetAttribute(AttributeKeyADCredentials, a)
+	if a.FullName != "" {
+		c.SetDisplayName(a.FullName)
+	}
+	if a.EffectiveName != "" {
+		c.SetUserName(a.EffectiveName)
+	}
+	for i := range a.GroupMembershipSIDs {
+		c.AddAuthzAttribute(a.GroupMembershipSIDs[i])
+	}
+}
+
+// GetADCredentials returns ADCredentials attributes sorted in the credential
+func (c *Credentials) GetADCredentials() ADCredentials {
+	if a, ok := c.attributes[AttributeKeyADCredentials].(ADCredentials); ok {
+		return a
+	}
+	return ADCredentials{}
+}
+
+// Methods to implement goidentity.Identity interface
+
+// UserName returns the credential's username.
+func (c *Credentials) UserName() string {
+	return c.username
+}
+
+// SetUserName sets the username value on the credential.
+func (c *Credentials) SetUserName(s string) {
+	c.username = s
+}
+
+// CName returns the credential's client principal name.
+func (c *Credentials) CName() types.PrincipalName {
+	return c.cname
+}
+
+// SetCName sets the client principal name on the credential.
+func (c *Credentials) SetCName(pn types.PrincipalName) {
+	c.cname = pn
+}
+
+// Domain returns the credential's domain.
+func (c *Credentials) Domain() string {
+	return c.realm
+}
+
+// SetDomain sets the domain value on the credential.
+func (c *Credentials) SetDomain(s string) {
+	c.realm = s
+}
+
+// Realm returns the credential's realm. Same as the domain.
+func (c *Credentials) Realm() string {
+	return c.Domain()
+}
+
+// SetRealm sets the realm value on the credential. Same as the domain
+func (c *Credentials) SetRealm(s string) {
+	c.SetDomain(s)
+}
+
+// DisplayName returns the credential's display name.
+func (c *Credentials) DisplayName() string {
+	return c.displayName
+}
+
+// SetDisplayName sets the display name value on the credential.
+func (c *Credentials) SetDisplayName(s string) {
+	c.displayName = s
+}
+
+// Human returns if the  credential represents a human or not.
+func (c *Credentials) Human() bool {
+	return c.human
+}
+
+// SetHuman sets the credential as human.
+func (c *Credentials) SetHuman(b bool) {
+	c.human = b
+}
+
+// AuthTime returns the time the credential was authenticated.
+func (c *Credentials) AuthTime() time.Time {
+	return c.authTime
+}
+
+// SetAuthTime sets the time the credential was authenticated.
+func (c *Credentials) SetAuthTime(t time.Time) {
+	c.authTime = t
+}
+
+// AuthzAttributes returns the credentials authorizing attributes.
+func (c *Credentials) AuthzAttributes() []string {
+	s := make([]string, len(c.groupMembership))
+	i := 0
+	for a := range c.groupMembership {
+		s[i] = a
+		i++
+	}
+	return s
+}
+
+// Authenticated indicates if the credential has been successfully authenticated or not.
+func (c *Credentials) Authenticated() bool {
+	return c.authenticated
+}
+
+// SetAuthenticated sets the credential as having been successfully authenticated.
+func (c *Credentials) SetAuthenticated(b bool) {
+	c.authenticated = b
+}
+
+// AddAuthzAttribute adds an authorization attribute to the credential.
+func (c *Credentials) AddAuthzAttribute(a string) {
+	c.groupMembership[a] = true
+}
+
+// RemoveAuthzAttribute removes an authorization attribute from the credential.
+func (c *Credentials) RemoveAuthzAttribute(a string) {
+	if _, ok := c.groupMembership[a]; !ok {
+		return
+	}
+	delete(c.groupMembership, a)
+}
+
+// EnableAuthzAttribute toggles an authorization attribute to an enabled state on the credential.
+func (c *Credentials) EnableAuthzAttribute(a string) {
+	if enabled, ok := c.groupMembership[a]; ok && !enabled {
+		c.groupMembership[a] = true
+	}
+}
+
+// DisableAuthzAttribute toggles an authorization attribute to a disabled state on the credential.
+func (c *Credentials) DisableAuthzAttribute(a string) {
+	if enabled, ok := c.groupMembership[a]; ok && enabled {
+		c.groupMembership[a] = false
+	}
+}
+
+// Authorized indicates if the credential has the specified authorizing attribute.
+func (c *Credentials) Authorized(a string) bool {
+	if enabled, ok := c.groupMembership[a]; ok && enabled {
+		return true
+	}
+	return false
+}
+
+// SessionID returns the credential's session ID.
+func (c *Credentials) SessionID() string {
+	return c.sessionID
+}
+
+// Expired indicates if the credential has expired.
+func (c *Credentials) Expired() bool {
+	if !c.validUntil.IsZero() && time.Now().UTC().After(c.validUntil) {
+		return true
+	}
+	return false
+}
+
+// ValidUntil returns the credential's valid until date
+func (c *Credentials) ValidUntil() time.Time {
+	return c.validUntil
+}
+
+// Attributes returns the Credentials' attributes map.
+func (c *Credentials) Attributes() map[string]interface{} {
+	return c.attributes
+}
+
+// SetAttribute sets the value of an attribute.
+func (c *Credentials) SetAttribute(k string, v interface{}) {
+	c.attributes[k] = v
+}
+
+// SetAttributes replaces the attributes map with the one provided.
+func (c *Credentials) SetAttributes(a map[string]interface{}) {
+	c.attributes = a
+}
+
+// RemoveAttribute deletes an attribute from the attribute map that has the key provided.
+func (c *Credentials) RemoveAttribute(k string) {
+	delete(c.attributes, k)
+}
+
+// Marshal the Credentials into a byte slice
+func (c *Credentials) Marshal() ([]byte, error) {
+	gob.Register(map[string]interface{}{})
+	gob.Register(ADCredentials{})
+	buf := new(bytes.Buffer)
+	enc := gob.NewEncoder(buf)
+	mc := marshalCredentials{
+		Username:        c.username,
+		DisplayName:     c.displayName,
+		Realm:           c.realm,
+		CName:           c.cname,
+		Keytab:          c.HasKeytab(),
+		Password:        c.HasPassword(),
+		Attributes:      c.attributes,
+		ValidUntil:      c.validUntil,
+		Authenticated:   c.authenticated,
+		Human:           c.human,
+		AuthTime:        c.authTime,
+		GroupMembership: c.groupMembership,
+		SessionID:       c.sessionID,
+	}
+	err := enc.Encode(&mc)
+	if err != nil {
+		return []byte{}, err
+	}
+	return buf.Bytes(), nil
+}
+
+// Unmarshal a byte slice into Credentials
+func (c *Credentials) Unmarshal(b []byte) error {
+	gob.Register(map[string]interface{}{})
+	gob.Register(ADCredentials{})
+	mc := new(marshalCredentials)
+	buf := bytes.NewBuffer(b)
+	dec := gob.NewDecoder(buf)
+	err := dec.Decode(mc)
+	if err != nil {
+		return err
+	}
+	c.username = mc.Username
+	c.displayName = mc.DisplayName
+	c.realm = mc.Realm
+	c.cname = mc.CName
+	c.attributes = mc.Attributes
+	c.validUntil = mc.ValidUntil
+	c.authenticated = mc.Authenticated
+	c.human = mc.Human
+	c.authTime = mc.AuthTime
+	c.groupMembership = mc.GroupMembership
+	c.sessionID = mc.SessionID
+	return nil
+}
+
+// JSON return details of the Credentials in a JSON format.
+func (c *Credentials) JSON() (string, error) {
+	mc := marshalCredentials{
+		Username:      c.username,
+		DisplayName:   c.displayName,
+		Realm:         c.realm,
+		CName:         c.cname,
+		Keytab:        c.HasKeytab(),
+		Password:      c.HasPassword(),
+		ValidUntil:    c.validUntil,
+		Authenticated: c.authenticated,
+		Human:         c.human,
+		AuthTime:      c.authTime,
+		SessionID:     c.sessionID,
+	}
+	b, err := json.MarshalIndent(mc, "", "  ")
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes128-cts-hmac-sha1-96.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes128-cts-hmac-sha1-96.go
new file mode 100644
index 0000000..dd8babd
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes128-cts-hmac-sha1-96.go
@@ -0,0 +1,129 @@
+package crypto
+
+import (
+	"crypto/aes"
+	"crypto/hmac"
+	"crypto/sha1"
+	"hash"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/common"
+	"github.com/jcmturner/gokrb5/v8/crypto/rfc3961"
+	"github.com/jcmturner/gokrb5/v8/crypto/rfc3962"
+	"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+)
+
+// RFC 3962
+
+// Aes128CtsHmacSha96 implements Kerberos encryption type aes128-cts-hmac-sha1-96
+type Aes128CtsHmacSha96 struct {
+}
+
+// GetETypeID returns the EType ID number.
+func (e Aes128CtsHmacSha96) GetETypeID() int32 {
+	return etypeID.AES128_CTS_HMAC_SHA1_96
+}
+
+// GetHashID returns the checksum type ID number.
+func (e Aes128CtsHmacSha96) GetHashID() int32 {
+	return chksumtype.HMAC_SHA1_96_AES128
+}
+
+// GetKeyByteSize returns the number of bytes for key of this etype.
+func (e Aes128CtsHmacSha96) GetKeyByteSize() int {
+	return 128 / 8
+}
+
+// GetKeySeedBitLength returns the number of bits for the seed for key generation.
+func (e Aes128CtsHmacSha96) GetKeySeedBitLength() int {
+	return e.GetKeyByteSize() * 8
+}
+
+// GetHashFunc returns the hash function for this etype.
+func (e Aes128CtsHmacSha96) GetHashFunc() func() hash.Hash {
+	return sha1.New
+}
+
+// GetMessageBlockByteSize returns the block size for the etype's messages.
+func (e Aes128CtsHmacSha96) GetMessageBlockByteSize() int {
+	return 1
+}
+
+// GetDefaultStringToKeyParams returns the default key derivation parameters in string form.
+func (e Aes128CtsHmacSha96) GetDefaultStringToKeyParams() string {
+	return "00001000"
+}
+
+// GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations.
+func (e Aes128CtsHmacSha96) GetConfounderByteSize() int {
+	return aes.BlockSize
+}
+
+// GetHMACBitLength returns the bit count size of the integrity hash.
+func (e Aes128CtsHmacSha96) GetHMACBitLength() int {
+	return 96
+}
+
+// GetCypherBlockBitLength returns the bit count size of the cypher block.
+func (e Aes128CtsHmacSha96) GetCypherBlockBitLength() int {
+	return aes.BlockSize * 8
+}
+
+// StringToKey returns a key derived from the string provided.
+func (e Aes128CtsHmacSha96) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) {
+	return rfc3962.StringToKey(secret, salt, s2kparams, e)
+}
+
+// RandomToKey returns a key from the bytes provided.
+func (e Aes128CtsHmacSha96) RandomToKey(b []byte) []byte {
+	return rfc3961.RandomToKey(b)
+}
+
+// EncryptData encrypts the data provided.
+func (e Aes128CtsHmacSha96) EncryptData(key, data []byte) ([]byte, []byte, error) {
+	return rfc3962.EncryptData(key, data, e)
+}
+
+// EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message.
+func (e Aes128CtsHmacSha96) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) {
+	return rfc3962.EncryptMessage(key, message, usage, e)
+}
+
+// DecryptData decrypts the data provided.
+func (e Aes128CtsHmacSha96) DecryptData(key, data []byte) ([]byte, error) {
+	return rfc3962.DecryptData(key, data, e)
+}
+
+// DecryptMessage decrypts the message provided and verifies the integrity of the message.
+func (e Aes128CtsHmacSha96) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) {
+	return rfc3962.DecryptMessage(key, ciphertext, usage, e)
+}
+
+// DeriveKey derives a key from the protocol key based on the usage value.
+func (e Aes128CtsHmacSha96) DeriveKey(protocolKey, usage []byte) ([]byte, error) {
+	return rfc3961.DeriveKey(protocolKey, usage, e)
+}
+
+// DeriveRandom generates data needed for key generation.
+func (e Aes128CtsHmacSha96) DeriveRandom(protocolKey, usage []byte) ([]byte, error) {
+	return rfc3961.DeriveRandom(protocolKey, usage, e)
+}
+
+// VerifyIntegrity checks the integrity of the plaintext message.
+func (e Aes128CtsHmacSha96) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool {
+	return rfc3961.VerifyIntegrity(protocolKey, ct, pt, usage, e)
+}
+
+// GetChecksumHash returns a keyed checksum hash of the bytes provided.
+func (e Aes128CtsHmacSha96) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) {
+	return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e)
+}
+
+// VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided.
+func (e Aes128CtsHmacSha96) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool {
+	c, err := e.GetChecksumHash(protocolKey, data, usage)
+	if err != nil {
+		return false
+	}
+	return hmac.Equal(chksum, c)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes128-cts-hmac-sha256-128.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes128-cts-hmac-sha256-128.go
new file mode 100644
index 0000000..b05af7d
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes128-cts-hmac-sha256-128.go
@@ -0,0 +1,132 @@
+package crypto
+
+import (
+	"crypto/aes"
+	"crypto/hmac"
+	"crypto/sha256"
+	"hash"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/common"
+	"github.com/jcmturner/gokrb5/v8/crypto/rfc8009"
+	"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+)
+
+// RFC https://tools.ietf.org/html/rfc8009
+
+// Aes128CtsHmacSha256128 implements Kerberos encryption type aes128-cts-hmac-sha256-128
+type Aes128CtsHmacSha256128 struct {
+}
+
+// GetETypeID returns the EType ID number.
+func (e Aes128CtsHmacSha256128) GetETypeID() int32 {
+	return etypeID.AES128_CTS_HMAC_SHA256_128
+}
+
+// GetHashID returns the checksum type ID number.
+func (e Aes128CtsHmacSha256128) GetHashID() int32 {
+	return chksumtype.HMAC_SHA256_128_AES128
+}
+
+// GetKeyByteSize returns the number of bytes for key of this etype.
+func (e Aes128CtsHmacSha256128) GetKeyByteSize() int {
+	return 128 / 8
+}
+
+// GetKeySeedBitLength returns the number of bits for the seed for key generation.
+func (e Aes128CtsHmacSha256128) GetKeySeedBitLength() int {
+	return e.GetKeyByteSize() * 8
+}
+
+// GetHashFunc returns the hash function for this etype.
+func (e Aes128CtsHmacSha256128) GetHashFunc() func() hash.Hash {
+	return sha256.New
+}
+
+// GetMessageBlockByteSize returns the block size for the etype's messages.
+func (e Aes128CtsHmacSha256128) GetMessageBlockByteSize() int {
+	return 1
+}
+
+// GetDefaultStringToKeyParams returns the default key derivation parameters in string form.
+func (e Aes128CtsHmacSha256128) GetDefaultStringToKeyParams() string {
+	return "00008000"
+}
+
+// GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations.
+func (e Aes128CtsHmacSha256128) GetConfounderByteSize() int {
+	return aes.BlockSize
+}
+
+// GetHMACBitLength returns the bit count size of the integrity hash.
+func (e Aes128CtsHmacSha256128) GetHMACBitLength() int {
+	return 128
+}
+
+// GetCypherBlockBitLength returns the bit count size of the cypher block.
+func (e Aes128CtsHmacSha256128) GetCypherBlockBitLength() int {
+	return aes.BlockSize * 8
+}
+
+// StringToKey returns a key derived from the string provided.
+func (e Aes128CtsHmacSha256128) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) {
+	saltp := rfc8009.GetSaltP(salt, "aes128-cts-hmac-sha256-128")
+	return rfc8009.StringToKey(secret, saltp, s2kparams, e)
+}
+
+// RandomToKey returns a key from the bytes provided.
+func (e Aes128CtsHmacSha256128) RandomToKey(b []byte) []byte {
+	return rfc8009.RandomToKey(b)
+}
+
+// EncryptData encrypts the data provided.
+func (e Aes128CtsHmacSha256128) EncryptData(key, data []byte) ([]byte, []byte, error) {
+	return rfc8009.EncryptData(key, data, e)
+}
+
+// EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message.
+func (e Aes128CtsHmacSha256128) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) {
+	return rfc8009.EncryptMessage(key, message, usage, e)
+}
+
+// DecryptData decrypts the data provided.
+func (e Aes128CtsHmacSha256128) DecryptData(key, data []byte) ([]byte, error) {
+	return rfc8009.DecryptData(key, data, e)
+}
+
+// DecryptMessage decrypts the message provided and verifies the integrity of the message.
+func (e Aes128CtsHmacSha256128) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) {
+	return rfc8009.DecryptMessage(key, ciphertext, usage, e)
+}
+
+// DeriveKey derives a key from the protocol key based on the usage value.
+func (e Aes128CtsHmacSha256128) DeriveKey(protocolKey, usage []byte) ([]byte, error) {
+	return rfc8009.DeriveKey(protocolKey, usage, e), nil
+}
+
+// DeriveRandom generates data needed for key generation.
+func (e Aes128CtsHmacSha256128) DeriveRandom(protocolKey, usage []byte) ([]byte, error) {
+	return rfc8009.DeriveRandom(protocolKey, usage, e)
+}
+
+// VerifyIntegrity checks the integrity of the ciphertext message.
+// As the hash is calculated over the iv concatenated with the AES cipher output not the plaintext the pt value to this
+// interface method is not use. Pass any []byte.
+func (e Aes128CtsHmacSha256128) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool {
+	// We don't need ib just there for the interface
+	return rfc8009.VerifyIntegrity(protocolKey, ct, usage, e)
+}
+
+// GetChecksumHash returns a keyed checksum hash of the bytes provided.
+func (e Aes128CtsHmacSha256128) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) {
+	return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e)
+}
+
+// VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided.
+func (e Aes128CtsHmacSha256128) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool {
+	c, err := e.GetChecksumHash(protocolKey, data, usage)
+	if err != nil {
+		return false
+	}
+	return hmac.Equal(chksum, c)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes256-cts-hmac-sha1-96.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes256-cts-hmac-sha1-96.go
new file mode 100644
index 0000000..45e439a
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes256-cts-hmac-sha1-96.go
@@ -0,0 +1,129 @@
+package crypto
+
+import (
+	"crypto/aes"
+	"crypto/hmac"
+	"crypto/sha1"
+	"hash"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/common"
+	"github.com/jcmturner/gokrb5/v8/crypto/rfc3961"
+	"github.com/jcmturner/gokrb5/v8/crypto/rfc3962"
+	"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+)
+
+// RFC 3962
+
+// Aes256CtsHmacSha96 implements Kerberos encryption type aes256-cts-hmac-sha1-96
+type Aes256CtsHmacSha96 struct {
+}
+
+// GetETypeID returns the EType ID number.
+func (e Aes256CtsHmacSha96) GetETypeID() int32 {
+	return etypeID.AES256_CTS_HMAC_SHA1_96
+}
+
+// GetHashID returns the checksum type ID number.
+func (e Aes256CtsHmacSha96) GetHashID() int32 {
+	return chksumtype.HMAC_SHA1_96_AES256
+}
+
+// GetKeyByteSize returns the number of bytes for key of this etype.
+func (e Aes256CtsHmacSha96) GetKeyByteSize() int {
+	return 256 / 8
+}
+
+// GetKeySeedBitLength returns the number of bits for the seed for key generation.
+func (e Aes256CtsHmacSha96) GetKeySeedBitLength() int {
+	return e.GetKeyByteSize() * 8
+}
+
+// GetHashFunc returns the hash function for this etype.
+func (e Aes256CtsHmacSha96) GetHashFunc() func() hash.Hash {
+	return sha1.New
+}
+
+// GetMessageBlockByteSize returns the block size for the etype's messages.
+func (e Aes256CtsHmacSha96) GetMessageBlockByteSize() int {
+	return 1
+}
+
+// GetDefaultStringToKeyParams returns the default key derivation parameters in string form.
+func (e Aes256CtsHmacSha96) GetDefaultStringToKeyParams() string {
+	return "00001000"
+}
+
+// GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations.
+func (e Aes256CtsHmacSha96) GetConfounderByteSize() int {
+	return aes.BlockSize
+}
+
+// GetHMACBitLength returns the bit count size of the integrity hash.
+func (e Aes256CtsHmacSha96) GetHMACBitLength() int {
+	return 96
+}
+
+// GetCypherBlockBitLength returns the bit count size of the cypher block.
+func (e Aes256CtsHmacSha96) GetCypherBlockBitLength() int {
+	return aes.BlockSize * 8
+}
+
+// StringToKey returns a key derived from the string provided.
+func (e Aes256CtsHmacSha96) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) {
+	return rfc3962.StringToKey(secret, salt, s2kparams, e)
+}
+
+// RandomToKey returns a key from the bytes provided.
+func (e Aes256CtsHmacSha96) RandomToKey(b []byte) []byte {
+	return rfc3961.RandomToKey(b)
+}
+
+// EncryptData encrypts the data provided.
+func (e Aes256CtsHmacSha96) EncryptData(key, data []byte) ([]byte, []byte, error) {
+	return rfc3962.EncryptData(key, data, e)
+}
+
+// EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message.
+func (e Aes256CtsHmacSha96) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) {
+	return rfc3962.EncryptMessage(key, message, usage, e)
+}
+
+// DecryptData decrypts the data provided.
+func (e Aes256CtsHmacSha96) DecryptData(key, data []byte) ([]byte, error) {
+	return rfc3962.DecryptData(key, data, e)
+}
+
+// DecryptMessage decrypts the message provided and verifies the integrity of the message.
+func (e Aes256CtsHmacSha96) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) {
+	return rfc3962.DecryptMessage(key, ciphertext, usage, e)
+}
+
+// DeriveKey derives a key from the protocol key based on the usage value.
+func (e Aes256CtsHmacSha96) DeriveKey(protocolKey, usage []byte) ([]byte, error) {
+	return rfc3961.DeriveKey(protocolKey, usage, e)
+}
+
+// DeriveRandom generates data needed for key generation.
+func (e Aes256CtsHmacSha96) DeriveRandom(protocolKey, usage []byte) ([]byte, error) {
+	return rfc3961.DeriveRandom(protocolKey, usage, e)
+}
+
+// VerifyIntegrity checks the integrity of the plaintext message.
+func (e Aes256CtsHmacSha96) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool {
+	return rfc3961.VerifyIntegrity(protocolKey, ct, pt, usage, e)
+}
+
+// GetChecksumHash returns a keyed checksum hash of the bytes provided.
+func (e Aes256CtsHmacSha96) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) {
+	return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e)
+}
+
+// VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided.
+func (e Aes256CtsHmacSha96) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool {
+	c, err := e.GetChecksumHash(protocolKey, data, usage)
+	if err != nil {
+		return false
+	}
+	return hmac.Equal(chksum, c)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes256-cts-hmac-sha384-192.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes256-cts-hmac-sha384-192.go
new file mode 100644
index 0000000..6a54475
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/aes256-cts-hmac-sha384-192.go
@@ -0,0 +1,132 @@
+package crypto
+
+import (
+	"crypto/aes"
+	"crypto/hmac"
+	"crypto/sha512"
+	"hash"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/common"
+	"github.com/jcmturner/gokrb5/v8/crypto/rfc8009"
+	"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+)
+
+// RFC https://tools.ietf.org/html/rfc8009
+
+// Aes256CtsHmacSha384192 implements Kerberos encryption type aes256-cts-hmac-sha384-192
+type Aes256CtsHmacSha384192 struct {
+}
+
+// GetETypeID returns the EType ID number.
+func (e Aes256CtsHmacSha384192) GetETypeID() int32 {
+	return etypeID.AES256_CTS_HMAC_SHA384_192
+}
+
+// GetHashID returns the checksum type ID number.
+func (e Aes256CtsHmacSha384192) GetHashID() int32 {
+	return chksumtype.HMAC_SHA384_192_AES256
+}
+
+// GetKeyByteSize returns the number of bytes for key of this etype.
+func (e Aes256CtsHmacSha384192) GetKeyByteSize() int {
+	return 192 / 8
+}
+
+// GetKeySeedBitLength returns the number of bits for the seed for key generation.
+func (e Aes256CtsHmacSha384192) GetKeySeedBitLength() int {
+	return e.GetKeyByteSize() * 8
+}
+
+// GetHashFunc returns the hash function for this etype.
+func (e Aes256CtsHmacSha384192) GetHashFunc() func() hash.Hash {
+	return sha512.New384
+}
+
+// GetMessageBlockByteSize returns the block size for the etype's messages.
+func (e Aes256CtsHmacSha384192) GetMessageBlockByteSize() int {
+	return 1
+}
+
+// GetDefaultStringToKeyParams returns the default key derivation parameters in string form.
+func (e Aes256CtsHmacSha384192) GetDefaultStringToKeyParams() string {
+	return "00008000"
+}
+
+// GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations.
+func (e Aes256CtsHmacSha384192) GetConfounderByteSize() int {
+	return aes.BlockSize
+}
+
+// GetHMACBitLength returns the bit count size of the integrity hash.
+func (e Aes256CtsHmacSha384192) GetHMACBitLength() int {
+	return 192
+}
+
+// GetCypherBlockBitLength returns the bit count size of the cypher block.
+func (e Aes256CtsHmacSha384192) GetCypherBlockBitLength() int {
+	return aes.BlockSize * 8
+}
+
+// StringToKey returns a key derived from the string provided.
+func (e Aes256CtsHmacSha384192) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) {
+	saltp := rfc8009.GetSaltP(salt, "aes256-cts-hmac-sha384-192")
+	return rfc8009.StringToKey(secret, saltp, s2kparams, e)
+}
+
+// RandomToKey returns a key from the bytes provided.
+func (e Aes256CtsHmacSha384192) RandomToKey(b []byte) []byte {
+	return rfc8009.RandomToKey(b)
+}
+
+// EncryptData encrypts the data provided.
+func (e Aes256CtsHmacSha384192) EncryptData(key, data []byte) ([]byte, []byte, error) {
+	return rfc8009.EncryptData(key, data, e)
+}
+
+// EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message.
+func (e Aes256CtsHmacSha384192) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) {
+	return rfc8009.EncryptMessage(key, message, usage, e)
+}
+
+// DecryptData decrypts the data provided.
+func (e Aes256CtsHmacSha384192) DecryptData(key, data []byte) ([]byte, error) {
+	return rfc8009.DecryptData(key, data, e)
+}
+
+// DecryptMessage decrypts the message provided and verifies the integrity of the message.
+func (e Aes256CtsHmacSha384192) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) {
+	return rfc8009.DecryptMessage(key, ciphertext, usage, e)
+}
+
+// DeriveKey derives a key from the protocol key based on the usage value.
+func (e Aes256CtsHmacSha384192) DeriveKey(protocolKey, usage []byte) ([]byte, error) {
+	return rfc8009.DeriveKey(protocolKey, usage, e), nil
+}
+
+// DeriveRandom generates data needed for key generation.
+func (e Aes256CtsHmacSha384192) DeriveRandom(protocolKey, usage []byte) ([]byte, error) {
+	return rfc8009.DeriveRandom(protocolKey, usage, e)
+}
+
+// VerifyIntegrity checks the integrity of the ciphertext message.
+// As the hash is calculated over the iv concatenated with the AES cipher output not the plaintext the pt value to this
+// interface method is not use. Pass any []byte.
+func (e Aes256CtsHmacSha384192) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool {
+	// We don't need ib just there for the interface
+	return rfc8009.VerifyIntegrity(protocolKey, ct, usage, e)
+}
+
+// GetChecksumHash returns a keyed checksum hash of the bytes provided.
+func (e Aes256CtsHmacSha384192) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) {
+	return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e)
+}
+
+// VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided.
+func (e Aes256CtsHmacSha384192) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool {
+	c, err := e.GetChecksumHash(protocolKey, data, usage)
+	if err != nil {
+		return false
+	}
+	return hmac.Equal(chksum, c)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/common/common.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/common/common.go
new file mode 100644
index 0000000..dab55be
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/common/common.go
@@ -0,0 +1,132 @@
+// Package common provides encryption methods common across encryption types
+package common
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"encoding/binary"
+	"encoding/hex"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+)
+
+// ZeroPad pads bytes with zeros to nearest multiple of message size m.
+func ZeroPad(b []byte, m int) ([]byte, error) {
+	if m <= 0 {
+		return nil, errors.New("Invalid message block size when padding")
+	}
+	if b == nil || len(b) == 0 {
+		return nil, errors.New("Data not valid to pad: Zero size")
+	}
+	if l := len(b) % m; l != 0 {
+		n := m - l
+		z := make([]byte, n)
+		b = append(b, z...)
+	}
+	return b, nil
+}
+
+// PKCS7Pad pads bytes according to RFC 2315 to nearest multiple of message size m.
+func PKCS7Pad(b []byte, m int) ([]byte, error) {
+	if m <= 0 {
+		return nil, errors.New("Invalid message block size when padding")
+	}
+	if b == nil || len(b) == 0 {
+		return nil, errors.New("Data not valid to pad: Zero size")
+	}
+	n := m - (len(b) % m)
+	pb := make([]byte, len(b)+n)
+	copy(pb, b)
+	copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
+	return pb, nil
+}
+
+// PKCS7Unpad removes RFC 2315 padding from byes where message size is m.
+func PKCS7Unpad(b []byte, m int) ([]byte, error) {
+	if m <= 0 {
+		return nil, errors.New("invalid message block size when unpadding")
+	}
+	if b == nil || len(b) == 0 {
+		return nil, errors.New("padded data not valid: Zero size")
+	}
+	if len(b)%m != 0 {
+		return nil, errors.New("padded data not valid: Not multiple of message block size")
+	}
+	c := b[len(b)-1]
+	n := int(c)
+	if n == 0 || n > len(b) {
+		return nil, errors.New("padded data not valid: Data may not have been padded")
+	}
+	for i := 0; i < n; i++ {
+		if b[len(b)-n+i] != c {
+			return nil, errors.New("padded data not valid")
+		}
+	}
+	return b[:len(b)-n], nil
+}
+
+// GetHash generates the keyed hash value according to the etype's hash function.
+func GetHash(pt, key []byte, usage []byte, etype etype.EType) ([]byte, error) {
+	k, err := etype.DeriveKey(key, usage)
+	if err != nil {
+		return nil, fmt.Errorf("unable to derive key for checksum: %v", err)
+	}
+	mac := hmac.New(etype.GetHashFunc(), k)
+	p := make([]byte, len(pt))
+	copy(p, pt)
+	mac.Write(p)
+	return mac.Sum(nil)[:etype.GetHMACBitLength()/8], nil
+}
+
+// GetChecksumHash returns a keyed checksum hash of the bytes provided.
+func GetChecksumHash(b, key []byte, usage uint32, etype etype.EType) ([]byte, error) {
+	return GetHash(b, key, GetUsageKc(usage), etype)
+}
+
+// GetIntegrityHash returns a keyed integrity hash of the bytes provided.
+func GetIntegrityHash(b, key []byte, usage uint32, etype etype.EType) ([]byte, error) {
+	return GetHash(b, key, GetUsageKi(usage), etype)
+}
+
+// VerifyChecksum compares the checksum of the msg bytes is the same as the checksum provided.
+func VerifyChecksum(key, chksum, msg []byte, usage uint32, etype etype.EType) bool {
+	//The encrypted message is a concatenation of the encrypted output and the hash HMAC.
+	expectedMAC, _ := GetChecksumHash(msg, key, usage, etype)
+	return hmac.Equal(chksum, expectedMAC)
+}
+
+// GetUsageKc returns the checksum key usage value for the usage number un.
+//
+// See RFC 3961 5.3 key-derivation function definition.
+func GetUsageKc(un uint32) []byte {
+	return getUsage(un, 0x99)
+}
+
+// GetUsageKe returns the encryption key usage value for the usage number un
+//
+// See RFC 3961 5.3 key-derivation function definition.
+func GetUsageKe(un uint32) []byte {
+	return getUsage(un, 0xAA)
+}
+
+// GetUsageKi returns the integrity key usage value for the usage number un
+//
+// See RFC 3961 5.3 key-derivation function definition.
+func GetUsageKi(un uint32) []byte {
+	return getUsage(un, 0x55)
+}
+
+func getUsage(un uint32, o byte) []byte {
+	var buf bytes.Buffer
+	binary.Write(&buf, binary.BigEndian, un)
+	return append(buf.Bytes(), o)
+}
+
+// IterationsToS2Kparams converts the number of iterations as an integer to a string representation.
+func IterationsToS2Kparams(i uint32) string {
+	b := make([]byte, 4, 4)
+	binary.BigEndian.PutUint32(b, i)
+	return hex.EncodeToString(b)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/crypto.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/crypto.go
new file mode 100644
index 0000000..5c96ddf
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/crypto.go
@@ -0,0 +1,175 @@
+// Package crypto implements cryptographic functions for Kerberos 5 implementation.
+package crypto
+
+import (
+	"encoding/hex"
+	"fmt"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+	"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+	"github.com/jcmturner/gokrb5/v8/iana/patype"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// GetEtype returns an instances of the required etype struct for the etype ID.
+func GetEtype(id int32) (etype.EType, error) {
+	switch id {
+	case etypeID.AES128_CTS_HMAC_SHA1_96:
+		var et Aes128CtsHmacSha96
+		return et, nil
+	case etypeID.AES256_CTS_HMAC_SHA1_96:
+		var et Aes256CtsHmacSha96
+		return et, nil
+	case etypeID.AES128_CTS_HMAC_SHA256_128:
+		var et Aes128CtsHmacSha256128
+		return et, nil
+	case etypeID.AES256_CTS_HMAC_SHA384_192:
+		var et Aes256CtsHmacSha384192
+		return et, nil
+	case etypeID.DES3_CBC_SHA1_KD:
+		var et Des3CbcSha1Kd
+		return et, nil
+	case etypeID.RC4_HMAC:
+		var et RC4HMAC
+		return et, nil
+	default:
+		return nil, fmt.Errorf("unknown or unsupported EType: %d", id)
+	}
+}
+
+// GetChksumEtype returns an instances of the required etype struct for the checksum ID.
+func GetChksumEtype(id int32) (etype.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
+	case chksumtype.HMAC_SHA256_128_AES128:
+		var et Aes128CtsHmacSha256128
+		return et, nil
+	case chksumtype.HMAC_SHA384_192_AES256:
+		var et Aes256CtsHmacSha384192
+		return et, nil
+	case chksumtype.HMAC_SHA1_DES3_KD:
+		var et Des3CbcSha1Kd
+		return et, nil
+	case chksumtype.KERB_CHECKSUM_HMAC_MD5:
+		var et RC4HMAC
+		return et, nil
+	//case chksumtype.KERB_CHECKSUM_HMAC_MD5_UNSIGNED:
+	//	var et RC4HMAC
+	//	return et, nil
+	default:
+		return nil, fmt.Errorf("unknown or unsupported checksum type: %d", id)
+	}
+}
+
+// GetKeyFromPassword generates an encryption key from the principal's password.
+func GetKeyFromPassword(passwd string, cname types.PrincipalName, realm string, etypeID int32, pas types.PADataSequence) (types.EncryptionKey, etype.EType, error) {
+	var key types.EncryptionKey
+	et, err := GetEtype(etypeID)
+	if err != nil {
+		return key, et, fmt.Errorf("error getting encryption type: %v", err)
+	}
+	sk2p := et.GetDefaultStringToKeyParams()
+	var salt string
+	var paID int32
+	for _, pa := range pas {
+		switch pa.PADataType {
+		case patype.PA_PW_SALT:
+			if paID > pa.PADataType {
+				continue
+			}
+			salt = string(pa.PADataValue)
+		case patype.PA_ETYPE_INFO:
+			if paID > pa.PADataType {
+				continue
+			}
+			var eti types.ETypeInfo
+			err := eti.Unmarshal(pa.PADataValue)
+			if err != nil {
+				return key, et, fmt.Errorf("error unmashaling PA Data to PA-ETYPE-INFO2: %v", err)
+			}
+			if etypeID != eti[0].EType {
+				et, err = GetEtype(eti[0].EType)
+				if err != nil {
+					return key, et, fmt.Errorf("error getting encryption type: %v", err)
+				}
+			}
+			salt = string(eti[0].Salt)
+		case patype.PA_ETYPE_INFO2:
+			if paID > pa.PADataType {
+				continue
+			}
+			var et2 types.ETypeInfo2
+			err := et2.Unmarshal(pa.PADataValue)
+			if err != nil {
+				return key, et, fmt.Errorf("error unmashalling PA Data to PA-ETYPE-INFO2: %v", err)
+			}
+			if etypeID != et2[0].EType {
+				et, err = GetEtype(et2[0].EType)
+				if err != nil {
+					return key, et, fmt.Errorf("error getting encryption type: %v", err)
+				}
+			}
+			if len(et2[0].S2KParams) == 4 {
+				sk2p = hex.EncodeToString(et2[0].S2KParams)
+			}
+			salt = et2[0].Salt
+		}
+	}
+	if salt == "" {
+		salt = cname.GetSalt(realm)
+	}
+	k, err := et.StringToKey(passwd, salt, sk2p)
+	if err != nil {
+		return key, et, fmt.Errorf("error deriving key from string: %+v", err)
+	}
+	key = types.EncryptionKey{
+		KeyType:  etypeID,
+		KeyValue: k,
+	}
+	return key, et, nil
+}
+
+// GetEncryptedData encrypts the data provided and returns and EncryptedData type.
+// Pass a usage value of zero to use the key provided directly rather than deriving one.
+func GetEncryptedData(plainBytes []byte, key types.EncryptionKey, usage uint32, kvno int) (types.EncryptedData, error) {
+	var ed types.EncryptedData
+	et, err := GetEtype(key.KeyType)
+	if err != nil {
+		return ed, fmt.Errorf("error getting etype: %v", err)
+	}
+	_, b, err := et.EncryptMessage(key.KeyValue, plainBytes, usage)
+	if err != nil {
+		return ed, err
+	}
+
+	ed = types.EncryptedData{
+		EType:  key.KeyType,
+		Cipher: b,
+		KVNO:   kvno,
+	}
+	return ed, nil
+}
+
+// DecryptEncPart decrypts the EncryptedData.
+func DecryptEncPart(ed types.EncryptedData, key types.EncryptionKey, usage uint32) ([]byte, error) {
+	return DecryptMessage(ed.Cipher, key, usage)
+}
+
+// DecryptMessage decrypts the ciphertext and verifies the integrity.
+func DecryptMessage(ciphertext []byte, key types.EncryptionKey, usage uint32) ([]byte, error) {
+	et, err := GetEtype(key.KeyType)
+	if err != nil {
+		return []byte{}, fmt.Errorf("error decrypting: %v", err)
+	}
+	b, err := et.DecryptMessage(key.KeyValue, ciphertext, usage)
+	if err != nil {
+		return nil, fmt.Errorf("error decrypting: %v", err)
+	}
+	return b, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/des3-cbc-sha1-kd.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/des3-cbc-sha1-kd.go
new file mode 100644
index 0000000..6e650eb
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/des3-cbc-sha1-kd.go
@@ -0,0 +1,139 @@
+package crypto
+
+import (
+	"crypto/des"
+	"crypto/hmac"
+	"crypto/sha1"
+	"errors"
+	"hash"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/common"
+	"github.com/jcmturner/gokrb5/v8/crypto/rfc3961"
+	"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+)
+
+//RFC: 3961 Section 6.3
+
+// Des3CbcSha1Kd implements Kerberos encryption type des3-cbc-hmac-sha1-kd
+type Des3CbcSha1Kd struct {
+}
+
+// GetETypeID returns the EType ID number.
+func (e Des3CbcSha1Kd) GetETypeID() int32 {
+	return etypeID.DES3_CBC_SHA1_KD
+}
+
+// GetHashID returns the checksum type ID number.
+func (e Des3CbcSha1Kd) GetHashID() int32 {
+	return chksumtype.HMAC_SHA1_DES3_KD
+}
+
+// GetKeyByteSize returns the number of bytes for key of this etype.
+func (e Des3CbcSha1Kd) GetKeyByteSize() int {
+	return 24
+}
+
+// GetKeySeedBitLength returns the number of bits for the seed for key generation.
+func (e Des3CbcSha1Kd) GetKeySeedBitLength() int {
+	return 21 * 8
+}
+
+// GetHashFunc returns the hash function for this etype.
+func (e Des3CbcSha1Kd) GetHashFunc() func() hash.Hash {
+	return sha1.New
+}
+
+// GetMessageBlockByteSize returns the block size for the etype's messages.
+func (e Des3CbcSha1Kd) GetMessageBlockByteSize() int {
+	//For traditional CBC mode with padding, it would be the underlying cipher's block size
+	return des.BlockSize
+}
+
+// GetDefaultStringToKeyParams returns the default key derivation parameters in string form.
+func (e Des3CbcSha1Kd) GetDefaultStringToKeyParams() string {
+	var s string
+	return s
+}
+
+// GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations.
+func (e Des3CbcSha1Kd) GetConfounderByteSize() int {
+	return des.BlockSize
+}
+
+// GetHMACBitLength returns the bit count size of the integrity hash.
+func (e Des3CbcSha1Kd) GetHMACBitLength() int {
+	return e.GetHashFunc()().Size() * 8
+}
+
+// GetCypherBlockBitLength returns the bit count size of the cypher block.
+func (e Des3CbcSha1Kd) GetCypherBlockBitLength() int {
+	return des.BlockSize * 8
+}
+
+// StringToKey returns a key derived from the string provided.
+func (e Des3CbcSha1Kd) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) {
+	if s2kparams != "" {
+		return []byte{}, errors.New("s2kparams must be an empty string")
+	}
+	return rfc3961.DES3StringToKey(secret, salt, e)
+}
+
+// RandomToKey returns a key from the bytes provided.
+func (e Des3CbcSha1Kd) RandomToKey(b []byte) []byte {
+	return rfc3961.DES3RandomToKey(b)
+}
+
+// DeriveRandom generates data needed for key generation.
+func (e Des3CbcSha1Kd) DeriveRandom(protocolKey, usage []byte) ([]byte, error) {
+	r, err := rfc3961.DeriveRandom(protocolKey, usage, e)
+	return r, err
+}
+
+// DeriveKey derives a key from the protocol key based on the usage value.
+func (e Des3CbcSha1Kd) DeriveKey(protocolKey, usage []byte) ([]byte, error) {
+	r, err := e.DeriveRandom(protocolKey, usage)
+	if err != nil {
+		return nil, err
+	}
+	return e.RandomToKey(r), nil
+}
+
+// EncryptData encrypts the data provided.
+func (e Des3CbcSha1Kd) EncryptData(key, data []byte) ([]byte, []byte, error) {
+	return rfc3961.DES3EncryptData(key, data, e)
+}
+
+// EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message.
+func (e Des3CbcSha1Kd) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) {
+	return rfc3961.DES3EncryptMessage(key, message, usage, e)
+}
+
+// DecryptData decrypts the data provided.
+func (e Des3CbcSha1Kd) DecryptData(key, data []byte) ([]byte, error) {
+	return rfc3961.DES3DecryptData(key, data, e)
+}
+
+// DecryptMessage decrypts the message provided and verifies the integrity of the message.
+func (e Des3CbcSha1Kd) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) {
+	return rfc3961.DES3DecryptMessage(key, ciphertext, usage, e)
+}
+
+// VerifyIntegrity checks the integrity of the plaintext message.
+func (e Des3CbcSha1Kd) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool {
+	return rfc3961.VerifyIntegrity(protocolKey, ct, pt, usage, e)
+}
+
+// GetChecksumHash returns a keyed checksum hash of the bytes provided.
+func (e Des3CbcSha1Kd) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) {
+	return common.GetHash(data, protocolKey, common.GetUsageKc(usage), e)
+}
+
+// VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided.
+func (e Des3CbcSha1Kd) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool {
+	c, err := e.GetChecksumHash(protocolKey, data, usage)
+	if err != nil {
+		return false
+	}
+	return hmac.Equal(chksum, c)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/etype/etype.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/etype/etype.go
new file mode 100644
index 0000000..ab1496d
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/etype/etype.go
@@ -0,0 +1,29 @@
+// Package etype provides the Kerberos Encryption Type interface
+package etype
+
+import "hash"
+
+// EType is the interface defining the Encryption Type.
+type EType interface {
+	GetETypeID() int32
+	GetHashID() int32
+	GetKeyByteSize() int
+	GetKeySeedBitLength() int
+	GetDefaultStringToKeyParams() string
+	StringToKey(string, salt, s2kparams string) ([]byte, error)
+	RandomToKey(b []byte) []byte
+	GetHMACBitLength() int
+	GetMessageBlockByteSize() int
+	EncryptData(key, data []byte) ([]byte, []byte, error)
+	EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error)
+	DecryptData(key, data []byte) ([]byte, error)
+	DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error)
+	GetCypherBlockBitLength() int
+	GetConfounderByteSize() int
+	DeriveKey(protocolKey, usage []byte) ([]byte, error)
+	DeriveRandom(protocolKey, usage []byte) ([]byte, error)
+	VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool
+	GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error)
+	VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool
+	GetHashFunc() func() hash.Hash
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rc4-hmac.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rc4-hmac.go
new file mode 100644
index 0000000..42f84b8
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rc4-hmac.go
@@ -0,0 +1,133 @@
+package crypto
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/md5"
+	"hash"
+	"io"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/rfc3961"
+	"github.com/jcmturner/gokrb5/v8/crypto/rfc4757"
+	"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+	"golang.org/x/crypto/md4"
+)
+
+// RC4HMAC implements Kerberos encryption type rc4-hmac
+type RC4HMAC struct {
+}
+
+// GetETypeID returns the EType ID number.
+func (e RC4HMAC) GetETypeID() int32 {
+	return etypeID.RC4_HMAC
+}
+
+// GetHashID returns the checksum type ID number.
+func (e RC4HMAC) GetHashID() int32 {
+	return chksumtype.KERB_CHECKSUM_HMAC_MD5
+}
+
+// GetKeyByteSize returns the number of bytes for key of this etype.
+func (e RC4HMAC) GetKeyByteSize() int {
+	return 16
+}
+
+// GetKeySeedBitLength returns the number of bits for the seed for key generation.
+func (e RC4HMAC) GetKeySeedBitLength() int {
+	return e.GetKeyByteSize() * 8
+}
+
+// GetHashFunc returns the hash function for this etype.
+func (e RC4HMAC) GetHashFunc() func() hash.Hash {
+	return md5.New
+}
+
+// GetMessageBlockByteSize returns the block size for the etype's messages.
+func (e RC4HMAC) GetMessageBlockByteSize() int {
+	return 1
+}
+
+// GetDefaultStringToKeyParams returns the default key derivation parameters in string form.
+func (e RC4HMAC) GetDefaultStringToKeyParams() string {
+	return ""
+}
+
+// GetConfounderByteSize returns the byte count for confounder to be used during cryptographic operations.
+func (e RC4HMAC) GetConfounderByteSize() int {
+	return 8
+}
+
+// GetHMACBitLength returns the bit count size of the integrity hash.
+func (e RC4HMAC) GetHMACBitLength() int {
+	return md5.Size * 8
+}
+
+// GetCypherBlockBitLength returns the bit count size of the cypher block.
+func (e RC4HMAC) GetCypherBlockBitLength() int {
+	return 8 // doesn't really apply
+}
+
+// StringToKey returns a key derived from the string provided.
+func (e RC4HMAC) StringToKey(secret string, salt string, s2kparams string) ([]byte, error) {
+	return rfc4757.StringToKey(secret)
+}
+
+// RandomToKey returns a key from the bytes provided.
+func (e RC4HMAC) RandomToKey(b []byte) []byte {
+	r := bytes.NewReader(b)
+	h := md4.New()
+	io.Copy(h, r)
+	return h.Sum(nil)
+}
+
+// EncryptData encrypts the data provided.
+func (e RC4HMAC) EncryptData(key, data []byte) ([]byte, []byte, error) {
+	b, err := rfc4757.EncryptData(key, data, e)
+	return []byte{}, b, err
+}
+
+// EncryptMessage encrypts the message provided and concatenates it with the integrity hash to create an encrypted message.
+func (e RC4HMAC) EncryptMessage(key, message []byte, usage uint32) ([]byte, []byte, error) {
+	b, err := rfc4757.EncryptMessage(key, message, usage, false, e)
+	return []byte{}, b, err
+}
+
+// DecryptData decrypts the data provided.
+func (e RC4HMAC) DecryptData(key, data []byte) ([]byte, error) {
+	return rfc4757.DecryptData(key, data, e)
+}
+
+// DecryptMessage decrypts the message provided and verifies the integrity of the message.
+func (e RC4HMAC) DecryptMessage(key, ciphertext []byte, usage uint32) ([]byte, error) {
+	return rfc4757.DecryptMessage(key, ciphertext, usage, false, e)
+}
+
+// DeriveKey derives a key from the protocol key based on the usage value.
+func (e RC4HMAC) DeriveKey(protocolKey, usage []byte) ([]byte, error) {
+	return rfc4757.HMAC(protocolKey, usage), nil
+}
+
+// DeriveRandom generates data needed for key generation.
+func (e RC4HMAC) DeriveRandom(protocolKey, usage []byte) ([]byte, error) {
+	return rfc3961.DeriveRandom(protocolKey, usage, e)
+}
+
+// VerifyIntegrity checks the integrity of the plaintext message.
+func (e RC4HMAC) VerifyIntegrity(protocolKey, ct, pt []byte, usage uint32) bool {
+	return rfc4757.VerifyIntegrity(protocolKey, pt, ct, e)
+}
+
+// GetChecksumHash returns a keyed checksum hash of the bytes provided.
+func (e RC4HMAC) GetChecksumHash(protocolKey, data []byte, usage uint32) ([]byte, error) {
+	return rfc4757.Checksum(protocolKey, usage, data)
+}
+
+// VerifyChecksum compares the checksum of the message bytes is the same as the checksum provided.
+func (e RC4HMAC) VerifyChecksum(protocolKey, data, chksum []byte, usage uint32) bool {
+	checksum, err := rfc4757.Checksum(protocolKey, usage, data)
+	if err != nil {
+		return false
+	}
+	return hmac.Equal(checksum, chksum)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3961/encryption.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3961/encryption.go
new file mode 100644
index 0000000..1383258
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3961/encryption.go
@@ -0,0 +1,119 @@
+// Package rfc3961 provides encryption and checksum methods as specified in RFC 3961
+package rfc3961
+
+import (
+	"crypto/cipher"
+	"crypto/des"
+	"crypto/hmac"
+	"crypto/rand"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/common"
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+)
+
+// DES3EncryptData encrypts the data provided using DES3 and methods specific to the etype provided.
+func DES3EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) {
+	if len(key) != e.GetKeyByteSize() {
+		return nil, nil, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
+	}
+	data, _ = common.ZeroPad(data, e.GetMessageBlockByteSize())
+
+	block, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return nil, nil, fmt.Errorf("error creating cipher: %v", err)
+	}
+
+	//RFC 3961: initial cipher state      All bits zero
+	ivz := make([]byte, des.BlockSize)
+
+	ct := make([]byte, len(data))
+	mode := cipher.NewCBCEncrypter(block, ivz)
+	mode.CryptBlocks(ct, data)
+	return ct[len(ct)-e.GetMessageBlockByteSize():], ct, nil
+}
+
+// DES3EncryptMessage encrypts the message provided using DES3 and methods specific to the etype provided.
+// The encrypted data is concatenated with its integrity hash to create an encrypted message.
+func DES3EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) {
+	//confounder
+	c := make([]byte, e.GetConfounderByteSize())
+	_, err := rand.Read(c)
+	if err != nil {
+		return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err)
+	}
+	plainBytes := append(c, message...)
+	plainBytes, _ = common.ZeroPad(plainBytes, e.GetMessageBlockByteSize())
+
+	// Derive key for encryption from usage
+	var k []byte
+	if usage != 0 {
+		k, err = e.DeriveKey(key, common.GetUsageKe(usage))
+		if err != nil {
+			return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err)
+		}
+	}
+
+	iv, b, err := e.EncryptData(k, plainBytes)
+	if err != nil {
+		return iv, b, fmt.Errorf("error encrypting data: %v", err)
+	}
+
+	// Generate and append integrity hash
+	ih, err := common.GetIntegrityHash(plainBytes, key, usage, e)
+	if err != nil {
+		return iv, b, fmt.Errorf("error encrypting data: %v", err)
+	}
+	b = append(b, ih...)
+	return iv, b, nil
+}
+
+// DES3DecryptData decrypts the data provided using DES3 and methods specific to the etype provided.
+func DES3DecryptData(key, data []byte, e etype.EType) ([]byte, error) {
+	if len(key) != e.GetKeyByteSize() {
+		return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
+	}
+
+	if len(data) < des.BlockSize || len(data)%des.BlockSize != 0 {
+		return []byte{}, errors.New("ciphertext is not a multiple of the block size")
+	}
+	block, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return []byte{}, fmt.Errorf("error creating cipher: %v", err)
+	}
+	pt := make([]byte, len(data))
+	ivz := make([]byte, des.BlockSize)
+	mode := cipher.NewCBCDecrypter(block, ivz)
+	mode.CryptBlocks(pt, data)
+	return pt, nil
+}
+
+// DES3DecryptMessage decrypts the message provided using DES3 and methods specific to the etype provided.
+// The integrity of the message is also verified.
+func DES3DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) {
+	//Derive the key
+	k, err := e.DeriveKey(key, common.GetUsageKe(usage))
+	if err != nil {
+		return nil, fmt.Errorf("error deriving key: %v", err)
+	}
+	// Strip off the checksum from the end
+	b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8])
+	if err != nil {
+		return nil, fmt.Errorf("error decrypting: %v", err)
+	}
+	//Verify checksum
+	if !e.VerifyIntegrity(key, ciphertext, b, usage) {
+		return nil, errors.New("error decrypting: integrity verification failed")
+	}
+	//Remove the confounder bytes
+	return b[e.GetConfounderByteSize():], nil
+}
+
+// VerifyIntegrity verifies the integrity of cipertext bytes ct.
+func VerifyIntegrity(key, ct, pt []byte, usage uint32, etype etype.EType) bool {
+	h := make([]byte, etype.GetHMACBitLength()/8)
+	copy(h, ct[len(ct)-etype.GetHMACBitLength()/8:])
+	expectedMAC, _ := common.GetIntegrityHash(pt, key, usage, etype)
+	return hmac.Equal(h, expectedMAC)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3961/keyDerivation.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3961/keyDerivation.go
new file mode 100644
index 0000000..ed9b169
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3961/keyDerivation.go
@@ -0,0 +1,169 @@
+package rfc3961
+
+import (
+	"bytes"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+)
+
+const (
+	prfconstant = "prf"
+)
+
+// DeriveRandom implements the RFC 3961 defined function: 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.
+//
+// n: block size in bits (not bytes) - note if you use something like aes.BlockSize this is in bytes.
+//
+// k: key length / key seed length in bits. Eg. for AES256 this value is 256.
+//
+// e: the encryption etype function to use.
+func DeriveRandom(key, usage []byte, e etype.EType) ([]byte, error) {
+	n := e.GetCypherBlockBitLength()
+	k := e.GetKeySeedBitLength()
+	//Ensure the usage constant is at least the size of the cypher block size. Pass it through the nfold algorithm that will "stretch" it if needs be.
+	nFoldUsage := Nfold(usage, n)
+	//k-truncate implemented by creating a byte array the size of k (k is in bits hence /8)
+	out := make([]byte, k/8)
+	// Keep feeding the output back into the encryption function until it is no longer short than k.
+	_, K, err := e.EncryptData(key, nFoldUsage)
+	if err != nil {
+		return out, err
+	}
+	for i := copy(out, K); i < len(out); {
+		_, K, _ = e.EncryptData(key, K)
+		i = i + copy(out[i:], K)
+	}
+	return out, nil
+}
+
+// DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods.
+func DeriveKey(protocolKey, usage []byte, e etype.EType) ([]byte, error) {
+	r, err := e.DeriveRandom(protocolKey, usage)
+	if err != nil {
+		return nil, err
+	}
+	return e.RandomToKey(r), nil
+}
+
+// RandomToKey returns a key from the bytes provided according to the definition in RFC 3961.
+func RandomToKey(b []byte) []byte {
+	return b
+}
+
+// DES3RandomToKey returns a key from the bytes provided according to the definition in RFC 3961 for DES3 etypes.
+func DES3RandomToKey(b []byte) []byte {
+	r := fixWeakKey(stretch56Bits(b[:7]))
+	r2 := fixWeakKey(stretch56Bits(b[7:14]))
+	r = append(r, r2...)
+	r3 := fixWeakKey(stretch56Bits(b[14:21]))
+	r = append(r, r3...)
+	return r
+}
+
+// DES3StringToKey returns a key derived from the string provided according to the definition in RFC 3961 for DES3 etypes.
+func DES3StringToKey(secret, salt string, e etype.EType) ([]byte, error) {
+	s := secret + salt
+	tkey := e.RandomToKey(Nfold([]byte(s), e.GetKeySeedBitLength()))
+	return e.DeriveKey(tkey, []byte("kerberos"))
+}
+
+// PseudoRandom function as defined in RFC 3961
+func PseudoRandom(key, b []byte, e etype.EType) ([]byte, error) {
+	h := e.GetHashFunc()()
+	h.Write(b)
+	tmp := h.Sum(nil)[:e.GetMessageBlockByteSize()]
+	k, err := e.DeriveKey(key, []byte(prfconstant))
+	if err != nil {
+		return []byte{}, err
+	}
+	_, prf, err := e.EncryptData(k, tmp)
+	if err != nil {
+		return []byte{}, err
+	}
+	return prf, nil
+}
+
+func stretch56Bits(b []byte) []byte {
+	d := make([]byte, len(b), len(b))
+	copy(d, b)
+	var lb byte
+	for i, v := range d {
+		bv, nb := calcEvenParity(v)
+		d[i] = nb
+		if bv != 0 {
+			lb = lb | (1 << uint(i+1))
+		} else {
+			lb = lb &^ (1 << uint(i+1))
+		}
+	}
+	_, lb = calcEvenParity(lb)
+	d = append(d, lb)
+	return d
+}
+
+func calcEvenParity(b byte) (uint8, uint8) {
+	lowestbit := b & 0x01
+	// c counter of 1s in the first 7 bits of the byte
+	var c int
+	// Iterate over the highest 7 bits (hence p starts at 1 not zero) and count the 1s.
+	for p := 1; p < 8; p++ {
+		v := b & (1 << uint(p))
+		if v != 0 {
+			c++
+		}
+	}
+	if c%2 == 0 {
+		//Even number of 1s so set parity to 1
+		b = b | 1
+	} else {
+		//Odd number of 1s so set parity to 0
+		b = b &^ 1
+	}
+	return lowestbit, b
+}
+
+func fixWeakKey(b []byte) []byte {
+	if weak(b) {
+		b[7] ^= 0xF0
+	}
+	return b
+}
+
+func weak(b []byte) bool {
+	// weak keys from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-67r1.pdf
+	weakKeys := [4][]byte{
+		{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01},
+		{0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE},
+		{0xE0, 0xE0, 0xE0, 0xE0, 0xF1, 0xF1, 0xF1, 0xF1},
+		{0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E},
+	}
+	semiWeakKeys := [12][]byte{
+		{0x01, 0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E},
+		{0x1F, 0x01, 0x1F, 0x01, 0x0E, 0x01, 0x0E, 0x01},
+		{0x01, 0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1},
+		{0xE0, 0x01, 0xE0, 0x01, 0xF1, 0x01, 0xF1, 0x01},
+		{0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE},
+		{0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01},
+		{0x1F, 0xE0, 0x1F, 0xE0, 0x0E, 0xF1, 0x0E, 0xF1},
+		{0xE0, 0x1F, 0xE0, 0x1F, 0xF1, 0x0E, 0xF1, 0x0E},
+		{0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E, 0xFE},
+		{0xFE, 0x1F, 0xFE, 0x1F, 0xFE, 0x0E, 0xFE, 0x0E},
+		{0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE},
+		{0xFE, 0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1},
+	}
+	for _, k := range weakKeys {
+		if bytes.Equal(b, k) {
+			return true
+		}
+	}
+	for _, k := range semiWeakKeys {
+		if bytes.Equal(b, k) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3961/nfold.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3961/nfold.go
new file mode 100644
index 0000000..9536b1e
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3961/nfold.go
@@ -0,0 +1,107 @@
+package rfc3961
+
+// Implementation of the n-fold algorithm as defined in RFC 3961.
+
+/* Credits
+This golang implementation of nfold used the following project for help with implementation detail.
+Although their source is in java it was helpful as a reference implementation of the RFC.
+You can find the source code of their open source project along with license information below.
+We acknowledge and are grateful to these developers for their contributions to open source
+
+Project: Apache Directory (http://http://directory.apache.org/)
+https://svn.apache.org/repos/asf/directory/apacheds/tags/1.5.1/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/crypto/encryption/NFold.java
+License: http://www.apache.org/licenses/LICENSE-2.0
+*/
+
+// Nfold expands the key to ensure it is not smaller than one cipher block.
+// Defined in RFC 3961.
+//
+// m input bytes that will be "stretched" to the least common multiple of n bits and the bit length of m.
+func Nfold(m []byte, n int) []byte {
+	k := len(m) * 8
+
+	//Get the lowest common multiple of the two bit sizes
+	lcm := lcm(n, k)
+	relicate := lcm / k
+	var sumBytes []byte
+
+	for i := 0; i < relicate; i++ {
+		rotation := 13 * i
+		sumBytes = append(sumBytes, rotateRight(m, rotation)...)
+	}
+
+	nfold := make([]byte, n/8)
+	sum := make([]byte, n/8)
+	for i := 0; i < lcm/n; i++ {
+		for j := 0; j < n/8; j++ {
+			sum[j] = sumBytes[j+(i*len(sum))]
+		}
+		nfold = onesComplementAddition(nfold, sum)
+	}
+	return nfold
+}
+
+func onesComplementAddition(n1, n2 []byte) []byte {
+	numBits := len(n1) * 8
+	out := make([]byte, numBits/8)
+	carry := 0
+	for i := numBits - 1; i > -1; i-- {
+		n1b := getBit(&n1, i)
+		n2b := getBit(&n2, i)
+		s := n1b + n2b + carry
+
+		if s == 0 || s == 1 {
+			setBit(&out, i, s)
+			carry = 0
+		} else if s == 2 {
+			carry = 1
+		} else if s == 3 {
+			setBit(&out, i, 1)
+			carry = 1
+		}
+	}
+	if carry == 1 {
+		carryArray := make([]byte, len(n1))
+		carryArray[len(carryArray)-1] = 1
+		out = onesComplementAddition(out, carryArray)
+	}
+	return out
+}
+
+func rotateRight(b []byte, step int) []byte {
+	out := make([]byte, len(b))
+	bitLen := len(b) * 8
+	for i := 0; i < bitLen; i++ {
+		v := getBit(&b, i)
+		setBit(&out, (i+step)%bitLen, v)
+	}
+	return out
+}
+
+func lcm(x, y int) int {
+	return (x * y) / gcd(x, y)
+}
+
+func gcd(x, y int) int {
+	for y != 0 {
+		x, y = y, x%y
+	}
+	return x
+}
+
+func getBit(b *[]byte, p int) int {
+	pByte := p / 8
+	pBit := uint(p % 8)
+	vByte := (*b)[pByte]
+	vInt := int(vByte >> (8 - (pBit + 1)) & 0x0001)
+	return vInt
+}
+
+func setBit(b *[]byte, p, v int) {
+	pByte := p / 8
+	pBit := uint(p % 8)
+	oldByte := (*b)[pByte]
+	var newByte byte
+	newByte = byte(v<<(8-(pBit+1))) | oldByte
+	(*b)[pByte] = newByte
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3962/encryption.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3962/encryption.go
new file mode 100644
index 0000000..5ff89e8
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3962/encryption.go
@@ -0,0 +1,89 @@
+// Package rfc3962 provides encryption and checksum methods as specified in RFC 3962
+package rfc3962
+
+import (
+	"crypto/rand"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/aescts/v2"
+	"github.com/jcmturner/gokrb5/v8/crypto/common"
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+)
+
+// EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 3962.
+func EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) {
+	if len(key) != e.GetKeyByteSize() {
+		return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
+	}
+	ivz := make([]byte, e.GetCypherBlockBitLength()/8)
+	return aescts.Encrypt(key, ivz, data)
+}
+
+// EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 3962.
+// The encrypted data is concatenated with its integrity hash to create an encrypted message.
+func EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) {
+	if len(key) != e.GetKeyByteSize() {
+		return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
+	}
+	//confounder
+	c := make([]byte, e.GetConfounderByteSize())
+	_, err := rand.Read(c)
+	if err != nil {
+		return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err)
+	}
+	plainBytes := append(c, message...)
+
+	// Derive key for encryption from usage
+	var k []byte
+	if usage != 0 {
+		k, err = e.DeriveKey(key, common.GetUsageKe(usage))
+		if err != nil {
+			return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err)
+		}
+	}
+
+	// Encrypt the data
+	iv, b, err := e.EncryptData(k, plainBytes)
+	if err != nil {
+		return iv, b, fmt.Errorf("error encrypting data: %v", err)
+	}
+
+	// Generate and append integrity hash
+	ih, err := common.GetIntegrityHash(plainBytes, key, usage, e)
+	if err != nil {
+		return iv, b, fmt.Errorf("error encrypting data: %v", err)
+	}
+	b = append(b, ih...)
+	return iv, b, nil
+}
+
+// DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 3962.
+func DecryptData(key, data []byte, e etype.EType) ([]byte, error) {
+	if len(key) != e.GetKeyByteSize() {
+		return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
+	}
+	ivz := make([]byte, e.GetCypherBlockBitLength()/8)
+	return aescts.Decrypt(key, ivz, data)
+}
+
+// DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 3962.
+// The integrity of the message is also verified.
+func DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) {
+	//Derive the key
+	k, err := e.DeriveKey(key, common.GetUsageKe(usage))
+	if err != nil {
+		return nil, fmt.Errorf("error deriving key: %v", err)
+	}
+	// Strip off the checksum from the end
+	b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8])
+	if err != nil {
+		return nil, err
+	}
+	//Verify checksum
+	if !e.VerifyIntegrity(key, ciphertext, b, usage) {
+		return nil, errors.New("integrity verification failed")
+	}
+	//Remove the confounder bytes
+	return b[e.GetConfounderByteSize():], nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3962/keyDerivation.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3962/keyDerivation.go
new file mode 100644
index 0000000..fb402d9
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc3962/keyDerivation.go
@@ -0,0 +1,51 @@
+package rfc3962
+
+import (
+	"encoding/binary"
+	"encoding/hex"
+	"errors"
+
+	"github.com/jcmturner/gofork/x/crypto/pbkdf2"
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+)
+
+const (
+	s2kParamsZero = 4294967296
+)
+
+// StringToKey returns a key derived from the string provided according to the definition in RFC 3961.
+func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) {
+	i, err := S2KparamsToItertions(s2kparams)
+	if err != nil {
+		return nil, err
+	}
+	return StringToKeyIter(secret, salt, i, e)
+}
+
+// StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0
+func StringToPBKDF2(secret, salt string, iterations int64, e etype.EType) []byte {
+	return pbkdf2.Key64([]byte(secret), []byte(salt), iterations, int64(e.GetKeyByteSize()), e.GetHashFunc())
+}
+
+// StringToKeyIter returns a key derived from the string provided according to the definition in RFC 3961.
+func StringToKeyIter(secret, salt string, iterations int64, e etype.EType) ([]byte, error) {
+	tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e))
+	return e.DeriveKey(tkey, []byte("kerberos"))
+}
+
+// S2KparamsToItertions converts the string representation of iterations to an integer
+func S2KparamsToItertions(s2kparams string) (int64, error) {
+	//The s2kparams string should be hex string representing 4 bytes
+	//The 4 bytes represent a number in big endian order
+	//If the value is zero then the number of iterations should be 4,294,967,296 (2^32)
+	var i uint32
+	if len(s2kparams) != 8 {
+		return int64(s2kParamsZero), errors.New("invalid s2kparams length")
+	}
+	b, err := hex.DecodeString(s2kparams)
+	if err != nil {
+		return int64(s2kParamsZero), errors.New("invalid s2kparams, cannot decode string to bytes")
+	}
+	i = binary.BigEndian.Uint32(b)
+	return int64(i), nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/checksum.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/checksum.go
new file mode 100644
index 0000000..45276e9
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/checksum.go
@@ -0,0 +1,40 @@
+package rfc4757
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/md5"
+	"io"
+)
+
+// Checksum returns a hash of the data in accordance with RFC 4757
+func Checksum(key []byte, usage uint32, data []byte) ([]byte, error) {
+	// Create hashing key
+	s := append([]byte(`signaturekey`), byte(0x00)) //includes zero octet at end
+	mac := hmac.New(md5.New, key)
+	mac.Write(s)
+	Ksign := mac.Sum(nil)
+
+	// Format data
+	tb := UsageToMSMsgType(usage)
+	p := append(tb, data...)
+	h := md5.New()
+	rb := bytes.NewReader(p)
+	_, err := io.Copy(h, rb)
+	if err != nil {
+		return []byte{}, err
+	}
+	tmp := h.Sum(nil)
+
+	// Generate HMAC
+	mac = hmac.New(md5.New, Ksign)
+	mac.Write(tmp)
+	return mac.Sum(nil), nil
+}
+
+// HMAC returns a keyed MD5 checksum of the data
+func HMAC(key []byte, data []byte) []byte {
+	mac := hmac.New(md5.New, key)
+	mac.Write(data)
+	return mac.Sum(nil)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/encryption.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/encryption.go
new file mode 100644
index 0000000..fdebe73
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/encryption.go
@@ -0,0 +1,80 @@
+// Package rfc4757 provides encryption and checksum methods as specified in RFC 4757
+package rfc4757
+
+import (
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/rc4"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+)
+
+// EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 4757.
+func EncryptData(key, data []byte, e etype.EType) ([]byte, error) {
+	if len(key) != e.GetKeyByteSize() {
+		return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
+	}
+	rc4Cipher, err := rc4.NewCipher(key)
+	if err != nil {
+		return []byte{}, fmt.Errorf("error creating RC4 cipher: %v", err)
+	}
+	ed := make([]byte, len(data))
+	copy(ed, data)
+	rc4Cipher.XORKeyStream(ed, ed)
+	rc4Cipher.Reset()
+	return ed, nil
+}
+
+// DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 4757.
+func DecryptData(key, data []byte, e etype.EType) ([]byte, error) {
+	return EncryptData(key, data, e)
+}
+
+// EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 4757.
+// The encrypted data is concatenated with its RC4 header containing integrity checksum and confounder to create an encrypted message.
+func EncryptMessage(key, data []byte, usage uint32, export bool, e etype.EType) ([]byte, error) {
+	confounder := make([]byte, e.GetConfounderByteSize()) // size = 8
+	_, err := rand.Read(confounder)
+	if err != nil {
+		return []byte{}, fmt.Errorf("error generating confounder: %v", err)
+	}
+	k1 := key
+	k2 := HMAC(k1, UsageToMSMsgType(usage))
+	toenc := append(confounder, data...)
+	chksum := HMAC(k2, toenc)
+	k3 := HMAC(k2, chksum)
+
+	ed, err := EncryptData(k3, toenc, e)
+	if err != nil {
+		return []byte{}, fmt.Errorf("error encrypting data: %v", err)
+	}
+
+	msg := append(chksum, ed...)
+	return msg, nil
+}
+
+// DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 4757.
+// The integrity of the message is also verified.
+func DecryptMessage(key, data []byte, usage uint32, export bool, e etype.EType) ([]byte, error) {
+	checksum := data[:e.GetHMACBitLength()/8]
+	ct := data[e.GetHMACBitLength()/8:]
+	_, k2, k3 := deriveKeys(key, checksum, usage, export)
+
+	pt, err := DecryptData(k3, ct, e)
+	if err != nil {
+		return []byte{}, fmt.Errorf("error decrypting data: %v", err)
+	}
+
+	if !VerifyIntegrity(k2, pt, data, e) {
+		return []byte{}, errors.New("integrity checksum incorrect")
+	}
+	return pt[e.GetConfounderByteSize():], nil
+}
+
+// VerifyIntegrity checks the integrity checksum of the data matches that calculated from the decrypted data.
+func VerifyIntegrity(key, pt, data []byte, e etype.EType) bool {
+	chksum := HMAC(key, pt)
+	return hmac.Equal(chksum, data[:e.GetHMACBitLength()/8])
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/keyDerivation.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/keyDerivation.go
new file mode 100644
index 0000000..d1f90c0
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/keyDerivation.go
@@ -0,0 +1,40 @@
+package rfc4757
+
+import (
+	"bytes"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"io"
+
+	"golang.org/x/crypto/md4"
+)
+
+// StringToKey returns a key derived from the string provided according to the definition in RFC 4757.
+func StringToKey(secret string) ([]byte, error) {
+	b := make([]byte, len(secret)*2, len(secret)*2)
+	for i, r := range secret {
+		u := fmt.Sprintf("%04x", r)
+		c, err := hex.DecodeString(u)
+		if err != nil {
+			return []byte{}, errors.New("character could not be encoded")
+		}
+		// Swap round the two bytes to make little endian as we put into byte slice
+		b[2*i] = c[1]
+		b[2*i+1] = c[0]
+	}
+	r := bytes.NewReader(b)
+	h := md4.New()
+	_, err := io.Copy(h, r)
+	if err != nil {
+		return []byte{}, err
+	}
+	return h.Sum(nil), nil
+}
+
+func deriveKeys(key, checksum []byte, usage uint32, export bool) (k1, k2, k3 []byte) {
+	k1 = key
+	k2 = HMAC(k1, UsageToMSMsgType(usage))
+	k3 = HMAC(k2, checksum)
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/msgtype.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/msgtype.go
new file mode 100644
index 0000000..068588d
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc4757/msgtype.go
@@ -0,0 +1,20 @@
+package rfc4757
+
+import "encoding/binary"
+
+// UsageToMSMsgType converts Kerberos key usage numbers to Microsoft message type encoded as a little-endian four byte slice.
+func UsageToMSMsgType(usage uint32) []byte {
+	// Translate usage numbers to the Microsoft T numbers
+	switch usage {
+	case 3:
+		usage = 8
+	case 9:
+		usage = 8
+	case 23:
+		usage = 13
+	}
+	// Now convert to bytes
+	tb := make([]byte, 4) // We force an int32 input so we can't go over 4 bytes
+	binary.PutUvarint(tb, uint64(usage))
+	return tb
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc8009/encryption.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc8009/encryption.go
new file mode 100644
index 0000000..54cff7b
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc8009/encryption.go
@@ -0,0 +1,125 @@
+// Package rfc8009 provides encryption and checksum methods as specified in RFC 8009
+package rfc8009
+
+import (
+	"crypto/aes"
+	"crypto/hmac"
+	"crypto/rand"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/aescts/v2"
+	"github.com/jcmturner/gokrb5/v8/crypto/common"
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+)
+
+// EncryptData encrypts the data provided using methods specific to the etype provided as defined in RFC 8009.
+func EncryptData(key, data []byte, e etype.EType) ([]byte, []byte, error) {
+	kl := e.GetKeyByteSize()
+	if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
+		kl = 32
+	}
+	if len(key) != kl {
+		return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", e.GetKeyByteSize(), len(key))
+	}
+	ivz := make([]byte, aes.BlockSize)
+	return aescts.Encrypt(key, ivz, data)
+}
+
+// EncryptMessage encrypts the message provided using the methods specific to the etype provided as defined in RFC 8009.
+// The encrypted data is concatenated with its integrity hash to create an encrypted message.
+func EncryptMessage(key, message []byte, usage uint32, e etype.EType) ([]byte, []byte, error) {
+	kl := e.GetKeyByteSize()
+	if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
+		kl = 32
+	}
+	if len(key) != kl {
+		return []byte{}, []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", kl, len(key))
+	}
+	if len(key) != e.GetKeyByteSize() {
+	}
+	//confounder
+	c := make([]byte, e.GetConfounderByteSize())
+	_, err := rand.Read(c)
+	if err != nil {
+		return []byte{}, []byte{}, fmt.Errorf("could not generate random confounder: %v", err)
+	}
+	plainBytes := append(c, message...)
+
+	// Derive key for encryption from usage
+	var k []byte
+	if usage != 0 {
+		k, err = e.DeriveKey(key, common.GetUsageKe(usage))
+		if err != nil {
+			return []byte{}, []byte{}, fmt.Errorf("error deriving key for encryption: %v", err)
+		}
+	}
+
+	// Encrypt the data
+	iv, b, err := e.EncryptData(k, plainBytes)
+	if err != nil {
+		return iv, b, fmt.Errorf("error encrypting data: %v", err)
+	}
+
+	ivz := make([]byte, e.GetConfounderByteSize())
+	ih, err := GetIntegityHash(ivz, b, key, usage, e)
+	if err != nil {
+		return iv, b, fmt.Errorf("error encrypting data: %v", err)
+	}
+	b = append(b, ih...)
+	return iv, b, nil
+}
+
+// DecryptData decrypts the data provided using the methods specific to the etype provided as defined in RFC 8009.
+func DecryptData(key, data []byte, e etype.EType) ([]byte, error) {
+	kl := e.GetKeyByteSize()
+	if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
+		kl = 32
+	}
+	if len(key) != kl {
+		return []byte{}, fmt.Errorf("incorrect keysize: expected: %v actual: %v", kl, len(key))
+	}
+	ivz := make([]byte, aes.BlockSize)
+	return aescts.Decrypt(key, ivz, data)
+}
+
+// DecryptMessage decrypts the message provided using the methods specific to the etype provided as defined in RFC 8009.
+// The integrity of the message is also verified.
+func DecryptMessage(key, ciphertext []byte, usage uint32, e etype.EType) ([]byte, error) {
+	//Derive the key
+	k, err := e.DeriveKey(key, common.GetUsageKe(usage))
+	if err != nil {
+		return nil, fmt.Errorf("error deriving key: %v", err)
+	}
+	// Strip off the checksum from the end
+	b, err := e.DecryptData(k, ciphertext[:len(ciphertext)-e.GetHMACBitLength()/8])
+	if err != nil {
+		return nil, err
+	}
+	//Verify checksum
+	if !e.VerifyIntegrity(key, ciphertext, b, usage) {
+		return nil, errors.New("integrity verification failed")
+	}
+	//Remove the confounder bytes
+	return b[e.GetConfounderByteSize():], nil
+}
+
+// GetIntegityHash returns a keyed integrity hash of the bytes provided as defined in RFC 8009
+func GetIntegityHash(iv, c, key []byte, usage uint32, e etype.EType) ([]byte, error) {
+	// Generate and append integrity hash
+	// Rather than calculating the hash over the confounder and plaintext
+	// it is calculated over the iv concatenated with the AES cipher output.
+	ib := append(iv, c...)
+	return common.GetIntegrityHash(ib, key, usage, e)
+}
+
+// VerifyIntegrity verifies the integrity of cipertext bytes ct.
+func VerifyIntegrity(key, ct []byte, usage uint32, etype etype.EType) bool {
+	h := make([]byte, etype.GetHMACBitLength()/8)
+	copy(h, ct[len(ct)-etype.GetHMACBitLength()/8:])
+	ivz := make([]byte, etype.GetConfounderByteSize())
+	ib := append(ivz, ct[:len(ct)-(etype.GetHMACBitLength()/8)]...)
+	expectedMAC, _ := common.GetIntegrityHash(ib, key, usage, etype)
+	return hmac.Equal(h, expectedMAC)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc8009/keyDerivation.go b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc8009/keyDerivation.go
new file mode 100644
index 0000000..e947322
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/crypto/rfc8009/keyDerivation.go
@@ -0,0 +1,135 @@
+package rfc8009
+
+import (
+	"crypto/hmac"
+	"encoding/binary"
+	"encoding/hex"
+	"errors"
+
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
+	"golang.org/x/crypto/pbkdf2"
+)
+
+const (
+	s2kParamsZero = 32768
+)
+
+// DeriveRandom for key derivation as defined in RFC 8009
+func DeriveRandom(protocolKey, usage []byte, e etype.EType) ([]byte, error) {
+	h := e.GetHashFunc()()
+	return KDF_HMAC_SHA2(protocolKey, []byte("prf"), usage, h.Size(), e), nil
+}
+
+// DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods.
+//
+// https://tools.ietf.org/html/rfc8009#section-5
+func DeriveKey(protocolKey, label []byte, e etype.EType) []byte {
+	var context []byte
+	var kl int
+	// Key length is longer for aes256-cts-hmac-sha384-192 is it is a Ke or from StringToKey (where label is "kerberos")
+	if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
+	Swtch:
+		switch label[len(label)-1] {
+		case 0x73:
+			// 0x73 is "s" so label could be kerberos meaning StringToKey so now check if the label is "kerberos"
+			kerblabel := []byte("kerberos")
+			if len(label) != len(kerblabel) {
+				break
+			}
+			for i, b := range label {
+				if b != kerblabel[i] {
+					kl = e.GetKeySeedBitLength()
+					break Swtch
+				}
+			}
+			if kl == 0 {
+				// This is StringToKey
+				kl = 256
+			}
+		case 0xAA:
+			// This is a Ke
+			kl = 256
+		}
+	}
+	if kl == 0 {
+		kl = e.GetKeySeedBitLength()
+	}
+	return e.RandomToKey(KDF_HMAC_SHA2(protocolKey, label, context, kl, e))
+}
+
+// RandomToKey returns a key from the bytes provided according to the definition in RFC 8009.
+func RandomToKey(b []byte) []byte {
+	return b
+}
+
+// StringToKey returns a key derived from the string provided according to the definition in RFC 8009.
+func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) {
+	i, err := S2KparamsToItertions(s2kparams)
+	if err != nil {
+		return nil, err
+	}
+	return StringToKeyIter(secret, salt, i, e)
+}
+
+// StringToKeyIter returns a key derived from the string provided according to the definition in RFC 8009.
+func StringToKeyIter(secret, salt string, iterations int, e etype.EType) ([]byte, error) {
+	tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e))
+	return e.DeriveKey(tkey, []byte("kerberos"))
+}
+
+// StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0
+func StringToPBKDF2(secret, salt string, iterations int, e etype.EType) []byte {
+	kl := e.GetKeyByteSize()
+	if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
+		kl = 32
+	}
+	return pbkdf2.Key([]byte(secret), []byte(salt), iterations, kl, e.GetHashFunc())
+}
+
+// KDF_HMAC_SHA2 key derivation: https://tools.ietf.org/html/rfc8009#section-3
+func KDF_HMAC_SHA2(protocolKey, label, context []byte, kl int, e etype.EType) []byte {
+	//k: Length in bits of the key to be outputted, expressed in big-endian binary representation in 4 bytes.
+	k := make([]byte, 4, 4)
+	binary.BigEndian.PutUint32(k, uint32(kl))
+
+	c := make([]byte, 4, 4)
+	binary.BigEndian.PutUint32(c, uint32(1))
+	c = append(c, label...)
+	c = append(c, byte(0))
+	if len(context) > 0 {
+		c = append(c, context...)
+	}
+	c = append(c, k...)
+
+	mac := hmac.New(e.GetHashFunc(), protocolKey)
+	mac.Write(c)
+	return mac.Sum(nil)[:(kl / 8)]
+}
+
+// GetSaltP returns the salt value based on the etype name: https://tools.ietf.org/html/rfc8009#section-4
+func GetSaltP(salt, ename string) string {
+	b := []byte(ename)
+	b = append(b, byte(0))
+	b = append(b, []byte(salt)...)
+	return string(b)
+}
+
+// S2KparamsToItertions converts the string representation of iterations to an integer for RFC 8009.
+func S2KparamsToItertions(s2kparams string) (int, error) {
+	var i uint32
+	if len(s2kparams) != 8 {
+		return s2kParamsZero, errors.New("Invalid s2kparams length")
+	}
+	b, err := hex.DecodeString(s2kparams)
+	if err != nil {
+		return s2kParamsZero, errors.New("Invalid s2kparams, cannot decode string to bytes")
+	}
+	i = binary.BigEndian.Uint32(b)
+	//buf := bytes.NewBuffer(b)
+	//err = binary.Read(buf, binary.BigEndian, &i)
+	if err != nil {
+		return s2kParamsZero, errors.New("Invalid s2kparams, cannot convert to big endian int32")
+	}
+	return int(i), nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/gssapi/MICToken.go b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/MICToken.go
new file mode 100644
index 0000000..ab8daa2
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/MICToken.go
@@ -0,0 +1,174 @@
+package gssapi
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"encoding/binary"
+	"encoding/hex"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// RFC 4121, section 4.2.6.1
+
+const (
+	// MICTokenFlagSentByAcceptor - this flag indicates the sender is the context acceptor.  When not set, it indicates the sender is the context initiator
+	MICTokenFlagSentByAcceptor = 1 << iota
+	// MICTokenFlagSealed - this flag indicates confidentiality is provided for.  It SHALL NOT be set in MIC tokens
+	MICTokenFlagSealed
+	// MICTokenFlagAcceptorSubkey - a subkey asserted by the context acceptor is used to protect the message
+	MICTokenFlagAcceptorSubkey
+)
+
+const (
+	micHdrLen = 16 // Length of the MIC Token's header
+)
+
+// MICToken represents a GSS API MIC token, as defined in RFC 4121.
+// It contains the header fields, the payload (this is not transmitted) and
+// the checksum, and provides the logic for converting to/from bytes plus
+// computing and verifying checksums
+type MICToken struct {
+	// const GSS Token ID: 0x0404
+	Flags byte // contains three flags: acceptor, sealed, acceptor subkey
+	// const Filler: 0xFF 0xFF 0xFF 0xFF 0xFF
+	SndSeqNum uint64 // sender's sequence number. big-endian
+	Payload   []byte // your data! :)
+	Checksum  []byte // checksum of { payload | header }
+}
+
+// Return the 2 bytes identifying a GSS API MIC token
+func getGSSMICTokenID() *[2]byte {
+	return &[2]byte{0x04, 0x04}
+}
+
+// Return the filler bytes used in header
+func fillerBytes() *[5]byte {
+	return &[5]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
+}
+
+// Marshal the MICToken into a byte slice.
+// The payload should have been set and the checksum computed, otherwise an error is returned.
+func (mt *MICToken) Marshal() ([]byte, error) {
+	if mt.Checksum == nil {
+		return nil, errors.New("checksum has not been set")
+	}
+
+	bytes := make([]byte, micHdrLen+len(mt.Checksum))
+	copy(bytes[0:micHdrLen], mt.getMICChecksumHeader()[:])
+	copy(bytes[micHdrLen:], mt.Checksum)
+
+	return bytes, nil
+}
+
+// SetChecksum uses the passed encryption key and key usage to compute the checksum over the payload and
+// the header, and sets the Checksum field of this MICToken.
+// If the payload has not been set or the checksum has already been set, an error is returned.
+func (mt *MICToken) SetChecksum(key types.EncryptionKey, keyUsage uint32) error {
+	if mt.Checksum != nil {
+		return errors.New("checksum has already been computed")
+	}
+	checksum, err := mt.checksum(key, keyUsage)
+	if err != nil {
+		return err
+	}
+	mt.Checksum = checksum
+	return nil
+}
+
+// Compute and return the checksum of this token, computed using the passed key and key usage.
+// Note: This will NOT update the struct's Checksum field.
+func (mt *MICToken) checksum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) {
+	if mt.Payload == nil {
+		return nil, errors.New("cannot compute checksum with uninitialized payload")
+	}
+	d := make([]byte, micHdrLen+len(mt.Payload))
+	copy(d[0:], mt.Payload)
+	copy(d[len(mt.Payload):], mt.getMICChecksumHeader())
+
+	encType, err := crypto.GetEtype(key.KeyType)
+	if err != nil {
+		return nil, err
+	}
+	return encType.GetChecksumHash(key.KeyValue, d, keyUsage)
+}
+
+// Build a header suitable for a checksum computation
+func (mt *MICToken) getMICChecksumHeader() []byte {
+	header := make([]byte, micHdrLen)
+	copy(header[0:2], getGSSMICTokenID()[:])
+	header[2] = mt.Flags
+	copy(header[3:8], fillerBytes()[:])
+	binary.BigEndian.PutUint64(header[8:16], mt.SndSeqNum)
+	return header
+}
+
+// Verify computes the token's checksum with the provided key and usage,
+// and compares it to the checksum present in the token.
+// In case of any failure, (false, err) is returned, with err an explanatory error.
+func (mt *MICToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) {
+	computed, err := mt.checksum(key, keyUsage)
+	if err != nil {
+		return false, err
+	}
+	if !hmac.Equal(computed, mt.Checksum) {
+		return false, fmt.Errorf(
+			"checksum mismatch. Computed: %s, Contained in token: %s",
+			hex.EncodeToString(computed), hex.EncodeToString(mt.Checksum))
+	}
+	return true, nil
+}
+
+// Unmarshal bytes into the corresponding MICToken.
+// If expectFromAcceptor is true we expect the token to have been emitted by the gss acceptor,
+// and will check the according flag, returning an error if the token does not match the expectation.
+func (mt *MICToken) Unmarshal(b []byte, expectFromAcceptor bool) error {
+	if len(b) < micHdrLen {
+		return errors.New("bytes shorter than header length")
+	}
+	if !bytes.Equal(getGSSMICTokenID()[:], b[0:2]) {
+		return fmt.Errorf("wrong Token ID, Expected %s, was %s",
+			hex.EncodeToString(getGSSMICTokenID()[:]),
+			hex.EncodeToString(b[0:2]))
+	}
+	flags := b[2]
+	isFromAcceptor := flags&MICTokenFlagSentByAcceptor != 0
+	if isFromAcceptor && !expectFromAcceptor {
+		return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
+	}
+	if !isFromAcceptor && expectFromAcceptor {
+		return errors.New("unexpected acceptor flag is not set: expecting a token from the acceptor, not in the initiator")
+	}
+	if !bytes.Equal(b[3:8], fillerBytes()[:]) {
+		return fmt.Errorf("unexpected filler bytes: expecting %s, was %s",
+			hex.EncodeToString(fillerBytes()[:]),
+			hex.EncodeToString(b[3:8]))
+	}
+
+	mt.Flags = flags
+	mt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
+	mt.Checksum = b[micHdrLen:]
+	return nil
+}
+
+// NewInitiatorMICToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum.
+// Other flags are set to 0.
+// Note that in certain circumstances you may need to provide a sequence number that has been defined earlier.
+// This is currently not supported.
+func NewInitiatorMICToken(payload []byte, key types.EncryptionKey) (*MICToken, error) {
+	token := MICToken{
+		Flags:     0x00,
+		SndSeqNum: 0,
+		Payload:   payload,
+	}
+
+	if err := token.SetChecksum(key, keyusage.GSSAPI_INITIATOR_SIGN); err != nil {
+		return nil, err
+	}
+
+	return &token, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/gssapi/README.md b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/README.md
new file mode 100644
index 0000000..8fdcf70
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/README.md
@@ -0,0 +1,20 @@
+# Notes on GSS-API Negotiation Mechanism
+https://tools.ietf.org/html/rfc4178
+
+Client sends an initial negotiation message to the server which specifies the list of mechanisms 
+the client can support in order of decreasing preference.
+This message is generated with the ``NewNegTokenInitKrb5`` method.
+The message generated by this function specifies only a kerberos v5 mechanism is supported.
+
+The RFC states that this message can optionally contain the initial mechanism token 
+for the preferred mechanism (KRB5 in this case) of the client. The ``NewNegTokenInitKrb5`` 
+includes this in the message.
+
+The server side responds to this message with a one of four messages:
+
+| Message Type/State | Description |
+|--------------------|-------------|
+| accept-completed | indicates that the initiator-selected mechanism was acceptable to the target, and that the security mechanism token embedded in the first negotiation message was sufficient to complete the authentication |
+| accept-incomplete | At least one more message is needed from the client to establish security context. |
+| reject | Negotiation is being terminated. |
+| request-mic | (this state can only be present in the first reply message from the target) indicates that the MIC token exchange is REQUIRED if per-message integrity services are available |
\ No newline at end of file
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/gssapi/contextFlags.go b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/contextFlags.go
new file mode 100644
index 0000000..8c91859
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/contextFlags.go
@@ -0,0 +1,27 @@
+package gssapi
+
+import "github.com/jcmturner/gofork/encoding/asn1"
+
+// GSS-API context flags assigned numbers.
+const (
+	ContextFlagDeleg    = 1
+	ContextFlagMutual   = 2
+	ContextFlagReplay   = 4
+	ContextFlagSequence = 8
+	ContextFlagConf     = 16
+	ContextFlagInteg    = 32
+	ContextFlagAnon     = 64
+)
+
+// ContextFlags flags for GSSAPI
+// DEPRECATED - do not use
+type ContextFlags asn1.BitString
+
+// NewContextFlags creates a new ContextFlags instance
+// DEPRECATED - do not use
+func NewContextFlags() ContextFlags {
+	var c ContextFlags
+	c.BitLength = 32
+	c.Bytes = make([]byte, 4)
+	return c
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/gssapi/gssapi.go b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/gssapi.go
new file mode 100644
index 0000000..8082231
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/gssapi.go
@@ -0,0 +1,202 @@
+// Package gssapi implements Generic Security Services Application Program Interface required for SPNEGO kerberos authentication.
+package gssapi
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+)
+
+// GSS-API OID names
+const (
+	// GSS-API OID names
+	OIDKRB5         OIDName = "KRB5"         // MechType OID for Kerberos 5
+	OIDMSLegacyKRB5 OIDName = "MSLegacyKRB5" // MechType OID for Kerberos 5
+	OIDSPNEGO       OIDName = "SPNEGO"
+	OIDGSSIAKerb    OIDName = "GSSIAKerb" // Indicates the client cannot get a service ticket and asks the server to serve as an intermediate to the target KDC. http://k5wiki.kerberos.org/wiki/Projects/IAKERB#IAKERB_mech
+)
+
+// GSS-API status values
+const (
+	StatusBadBindings = 1 << iota
+	StatusBadMech
+	StatusBadName
+	StatusBadNameType
+	StatusBadStatus
+	StatusBadSig
+	StatusBadMIC
+	StatusContextExpired
+	StatusCredentialsExpired
+	StatusDefectiveCredential
+	StatusDefectiveToken
+	StatusFailure
+	StatusNoContext
+	StatusNoCred
+	StatusBadQOP
+	StatusUnauthorized
+	StatusUnavailable
+	StatusDuplicateElement
+	StatusNameNotMN
+	StatusComplete
+	StatusContinueNeeded
+	StatusDuplicateToken
+	StatusOldToken
+	StatusUnseqToken
+	StatusGapToken
+)
+
+// ContextToken is an interface for a GSS-API context token.
+type ContextToken interface {
+	Marshal() ([]byte, error)
+	Unmarshal(b []byte) error
+	Verify() (bool, Status)
+	Context() context.Context
+}
+
+/*
+CREDENTIAL MANAGEMENT
+
+GSS_Acquire_cred             acquire credentials for use
+GSS_Release_cred             release credentials after use
+GSS_Inquire_cred             display information about credentials
+GSS_Add_cred                 construct credentials incrementally
+GSS_Inquire_cred_by_mech     display per-mechanism credential information
+
+CONTEXT-LEVEL CALLS
+
+GSS_Init_sec_context         initiate outbound security context
+GSS_Accept_sec_context       accept inbound security context
+GSS_Delete_sec_context       flush context when no longer needed
+GSS_Process_context_token    process received control token on context
+GSS_Context_time             indicate validity time remaining on context
+GSS_Inquire_context          display information about context
+GSS_Wrap_size_limit          determine GSS_Wrap token size limit
+GSS_Export_sec_context       transfer context to other process
+GSS_Import_sec_context       import transferred context
+
+PER-MESSAGE CALLS
+
+GSS_GetMIC                   apply integrity check, receive as token separate from message
+GSS_VerifyMIC                validate integrity check token along with message
+GSS_Wrap                     sign, optionally encrypt, encapsulate
+GSS_Unwrap                   decapsulate, decrypt if needed, validate integrity check
+
+SUPPORT CALLS
+
+GSS_Display_status           translate status codes to printable form
+GSS_Indicate_mechs           indicate mech_types supported on local system
+GSS_Compare_name             compare two names for equality
+GSS_Display_name             translate name to printable form
+GSS_Import_name              convert printable name to normalized form
+GSS_Release_name             free storage of normalized-form name
+GSS_Release_buffer           free storage of general GSS-allocated object
+GSS_Release_OID_set          free storage of OID set object
+GSS_Create_empty_OID_set     create empty OID set
+GSS_Add_OID_set_member       add member to OID set
+GSS_Test_OID_set_member      test if OID is member of OID set
+GSS_Inquire_names_for_mech   indicate name types supported by mechanism
+GSS_Inquire_mechs_for_name   indicates mechanisms supporting name type
+GSS_Canonicalize_name        translate name to per-mechanism form
+GSS_Export_name              externalize per-mechanism name
+GSS_Duplicate_name           duplicate name object
+*/
+
+// Mechanism is the GSS-API interface for authentication mechanisms.
+type Mechanism interface {
+	OID() asn1.ObjectIdentifier
+	AcquireCred() error                                               // acquire credentials for use (eg. AS exchange for KRB5)
+	InitSecContext() (ContextToken, error)                            // initiate outbound security context (eg TGS exchange builds AP_REQ to go into ContextToken to send to service)
+	AcceptSecContext(ct ContextToken) (bool, context.Context, Status) // service verifies the token server side to establish a context
+	MIC() MICToken                                                    // apply integrity check, receive as token separate from message
+	VerifyMIC(mt MICToken) (bool, error)                              // validate integrity check token along with message
+	Wrap(msg []byte) WrapToken                                        // sign, optionally encrypt, encapsulate
+	Unwrap(wt WrapToken) []byte                                       // decapsulate, decrypt if needed, validate integrity check
+}
+
+// OIDName is the type for defined GSS-API OIDs.
+type OIDName string
+
+// OID returns the OID for the provided OID name.
+func (o OIDName) OID() asn1.ObjectIdentifier {
+	switch o {
+	case OIDSPNEGO:
+		return asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 2}
+	case OIDKRB5:
+		return asn1.ObjectIdentifier{1, 2, 840, 113554, 1, 2, 2}
+	case OIDMSLegacyKRB5:
+		return asn1.ObjectIdentifier{1, 2, 840, 48018, 1, 2, 2}
+	case OIDGSSIAKerb:
+		return asn1.ObjectIdentifier{1, 3, 6, 1, 5, 2, 5}
+	}
+	return asn1.ObjectIdentifier{}
+}
+
+// Status is the GSS-API status and implements the error interface.
+type Status struct {
+	Code    int
+	Message string
+}
+
+// Error returns the Status description.
+func (s Status) Error() string {
+	var str string
+	switch s.Code {
+	case StatusBadBindings:
+		str = "channel binding mismatch"
+	case StatusBadMech:
+		str = "unsupported mechanism requested"
+	case StatusBadName:
+		str = "invalid name provided"
+	case StatusBadNameType:
+		str = "name of unsupported type provided"
+	case StatusBadStatus:
+		str = "invalid input status selector"
+	case StatusBadSig:
+		str = "token had invalid integrity check"
+	case StatusBadMIC:
+		str = "preferred alias for GSS_S_BAD_SIG"
+	case StatusContextExpired:
+		str = "specified security context expired"
+	case StatusCredentialsExpired:
+		str = "expired credentials detected"
+	case StatusDefectiveCredential:
+		str = "defective credential detected"
+	case StatusDefectiveToken:
+		str = "defective token detected"
+	case StatusFailure:
+		str = "failure, unspecified at GSS-API level"
+	case StatusNoContext:
+		str = "no valid security context specified"
+	case StatusNoCred:
+		str = "no valid credentials provided"
+	case StatusBadQOP:
+		str = "unsupported QOP valu"
+	case StatusUnauthorized:
+		str = "operation unauthorized"
+	case StatusUnavailable:
+		str = "operation unavailable"
+	case StatusDuplicateElement:
+		str = "duplicate credential element requested"
+	case StatusNameNotMN:
+		str = "name contains multi-mechanism elements"
+	case StatusComplete:
+		str = "normal completion"
+	case StatusContinueNeeded:
+		str = "continuation call to routine required"
+	case StatusDuplicateToken:
+		str = "duplicate per-message token detected"
+	case StatusOldToken:
+		str = "timed-out per-message token detected"
+	case StatusUnseqToken:
+		str = "reordered (early) per-message token detected"
+	case StatusGapToken:
+		str = "skipped predecessor token(s) detected"
+	default:
+		str = "unknown GSS-API error status"
+	}
+	if s.Message != "" {
+		return fmt.Sprintf("%s: %s", str, s.Message)
+	}
+	return str
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/gssapi/wrapToken.go b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/wrapToken.go
new file mode 100644
index 0000000..ea7d054
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/gssapi/wrapToken.go
@@ -0,0 +1,195 @@
+package gssapi
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"encoding/binary"
+	"encoding/hex"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// RFC 4121, section 4.2.6.2
+
+const (
+	// HdrLen is the length of the Wrap Token's header
+	HdrLen = 16
+	// FillerByte is a filler in the WrapToken structure
+	FillerByte byte = 0xFF
+)
+
+// WrapToken represents a GSS API Wrap token, as defined in RFC 4121.
+// It contains the header fields, the payload and the checksum, and provides
+// the logic for converting to/from bytes plus computing and verifying checksums
+type WrapToken struct {
+	// const GSS Token ID: 0x0504
+	Flags byte // contains three flags: acceptor, sealed, acceptor subkey
+	// const Filler: 0xFF
+	EC        uint16 // checksum length. big-endian
+	RRC       uint16 // right rotation count. big-endian
+	SndSeqNum uint64 // sender's sequence number. big-endian
+	Payload   []byte // your data! :)
+	CheckSum  []byte // authenticated checksum of { payload | header }
+}
+
+// Return the 2 bytes identifying a GSS API Wrap token
+func getGssWrapTokenId() *[2]byte {
+	return &[2]byte{0x05, 0x04}
+}
+
+// Marshal the WrapToken into a byte slice.
+// The payload should have been set and the checksum computed, otherwise an error is returned.
+func (wt *WrapToken) Marshal() ([]byte, error) {
+	if wt.CheckSum == nil {
+		return nil, errors.New("checksum has not been set")
+	}
+	if wt.Payload == nil {
+		return nil, errors.New("payload has not been set")
+	}
+
+	pldOffset := HdrLen                    // Offset of the payload in the token
+	chkSOffset := HdrLen + len(wt.Payload) // Offset of the checksum in the token
+
+	bytes := make([]byte, chkSOffset+int(wt.EC))
+	copy(bytes[0:], getGssWrapTokenId()[:])
+	bytes[2] = wt.Flags
+	bytes[3] = FillerByte
+	binary.BigEndian.PutUint16(bytes[4:6], wt.EC)
+	binary.BigEndian.PutUint16(bytes[6:8], wt.RRC)
+	binary.BigEndian.PutUint64(bytes[8:16], wt.SndSeqNum)
+	copy(bytes[pldOffset:], wt.Payload)
+	copy(bytes[chkSOffset:], wt.CheckSum)
+	return bytes, nil
+}
+
+// SetCheckSum uses the passed encryption key and key usage to compute the checksum over the payload and
+// the header, and sets the CheckSum field of this WrapToken.
+// If the payload has not been set or the checksum has already been set, an error is returned.
+func (wt *WrapToken) SetCheckSum(key types.EncryptionKey, keyUsage uint32) error {
+	if wt.Payload == nil {
+		return errors.New("payload has not been set")
+	}
+	if wt.CheckSum != nil {
+		return errors.New("checksum has already been computed")
+	}
+	chkSum, cErr := wt.computeCheckSum(key, keyUsage)
+	if cErr != nil {
+		return cErr
+	}
+	wt.CheckSum = chkSum
+	return nil
+}
+
+// ComputeCheckSum computes and returns the checksum of this token, computed using the passed key and key usage.
+// Note: This will NOT update the struct's Checksum field.
+func (wt *WrapToken) computeCheckSum(key types.EncryptionKey, keyUsage uint32) ([]byte, error) {
+	if wt.Payload == nil {
+		return nil, errors.New("cannot compute checksum with uninitialized payload")
+	}
+	// Build a slice containing { payload | header }
+	checksumMe := make([]byte, HdrLen+len(wt.Payload))
+	copy(checksumMe[0:], wt.Payload)
+	copy(checksumMe[len(wt.Payload):], getChecksumHeader(wt.Flags, wt.SndSeqNum))
+
+	encType, err := crypto.GetEtype(key.KeyType)
+	if err != nil {
+		return nil, err
+	}
+	return encType.GetChecksumHash(key.KeyValue, checksumMe, keyUsage)
+}
+
+// Build a header suitable for a checksum computation
+func getChecksumHeader(flags byte, senderSeqNum uint64) []byte {
+	header := make([]byte, 16)
+	copy(header[0:], []byte{0x05, 0x04, flags, 0xFF, 0x00, 0x00, 0x00, 0x00})
+	binary.BigEndian.PutUint64(header[8:], senderSeqNum)
+	return header
+}
+
+// Verify computes the token's checksum with the provided key and usage,
+// and compares it to the checksum present in the token.
+// In case of any failure, (false, Err) is returned, with Err an explanatory error.
+func (wt *WrapToken) Verify(key types.EncryptionKey, keyUsage uint32) (bool, error) {
+	computed, cErr := wt.computeCheckSum(key, keyUsage)
+	if cErr != nil {
+		return false, cErr
+	}
+	if !hmac.Equal(computed, wt.CheckSum) {
+		return false, fmt.Errorf(
+			"checksum mismatch. Computed: %s, Contained in token: %s",
+			hex.EncodeToString(computed), hex.EncodeToString(wt.CheckSum))
+	}
+	return true, nil
+}
+
+// Unmarshal bytes into the corresponding WrapToken.
+// If expectFromAcceptor is true, we expect the token to have been emitted by the gss acceptor,
+// and will check the according flag, returning an error if the token does not match the expectation.
+func (wt *WrapToken) Unmarshal(b []byte, expectFromAcceptor bool) error {
+	// Check if we can read a whole header
+	if len(b) < 16 {
+		return errors.New("bytes shorter than header length")
+	}
+	// Is the Token ID correct?
+	if !bytes.Equal(getGssWrapTokenId()[:], b[0:2]) {
+		return fmt.Errorf("wrong Token ID. Expected %s, was %s",
+			hex.EncodeToString(getGssWrapTokenId()[:]),
+			hex.EncodeToString(b[0:2]))
+	}
+	// Check the acceptor flag
+	flags := b[2]
+	isFromAcceptor := flags&0x01 == 1
+	if isFromAcceptor && !expectFromAcceptor {
+		return errors.New("unexpected acceptor flag is set: not expecting a token from the acceptor")
+	}
+	if !isFromAcceptor && expectFromAcceptor {
+		return errors.New("expected acceptor flag is not set: expecting a token from the acceptor, not the initiator")
+	}
+	// Check the filler byte
+	if b[3] != FillerByte {
+		return fmt.Errorf("unexpected filler byte: expecting 0xFF, was %s ", hex.EncodeToString(b[3:4]))
+	}
+	checksumL := binary.BigEndian.Uint16(b[4:6])
+	// Sanity check on the checksum length
+	if int(checksumL) > len(b)-HdrLen {
+		return fmt.Errorf("inconsistent checksum length: %d bytes to parse, checksum length is %d", len(b), checksumL)
+	}
+
+	wt.Flags = flags
+	wt.EC = checksumL
+	wt.RRC = binary.BigEndian.Uint16(b[6:8])
+	wt.SndSeqNum = binary.BigEndian.Uint64(b[8:16])
+	wt.Payload = b[16 : len(b)-int(checksumL)]
+	wt.CheckSum = b[len(b)-int(checksumL):]
+	return nil
+}
+
+// NewInitiatorWrapToken builds a new initiator token (acceptor flag will be set to 0) and computes the authenticated checksum.
+// Other flags are set to 0, and the RRC and sequence number are initialized to 0.
+// Note that in certain circumstances you may need to provide a sequence number that has been defined earlier.
+// This is currently not supported.
+func NewInitiatorWrapToken(payload []byte, key types.EncryptionKey) (*WrapToken, error) {
+	encType, err := crypto.GetEtype(key.KeyType)
+	if err != nil {
+		return nil, err
+	}
+
+	token := WrapToken{
+		Flags: 0x00, // all zeroed out (this is a token sent by the initiator)
+		// Checksum size: length of output of the HMAC function, in bytes.
+		EC:        uint16(encType.GetHMACBitLength() / 8),
+		RRC:       0,
+		SndSeqNum: 0,
+		Payload:   payload,
+	}
+
+	if err := token.SetCheckSum(key, keyusage.GSSAPI_INITIATOR_SEAL); err != nil {
+		return nil, err
+	}
+
+	return &token, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/addrtype/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/addrtype/constants.go
new file mode 100644
index 0000000..457b89d
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/addrtype/constants.go
@@ -0,0 +1,15 @@
+// Package addrtype provides Address type assigned numbers.
+package addrtype
+
+// Address type IDs.
+const (
+	IPv4          int32 = 2
+	Directional   int32 = 3
+	ChaosNet      int32 = 5
+	XNS           int32 = 6
+	ISO           int32 = 7
+	DECNETPhaseIV int32 = 12
+	AppleTalkDDP  int32 = 16
+	NetBios       int32 = 20
+	IPv6          int32 = 24
+)
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/adtype/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/adtype/constants.go
new file mode 100644
index 0000000..e805b74
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/adtype/constants.go
@@ -0,0 +1,23 @@
+// Package adtype provides Authenticator type assigned numbers.
+package adtype
+
+// Authenticator type IDs.
+const (
+	ADIfRelevant                  int32 = 1
+	ADIntendedForServer           int32 = 2
+	ADIntendedForApplicationClass int32 = 3
+	ADKDCIssued                   int32 = 4
+	ADAndOr                       int32 = 5
+	ADMandatoryTicketExtensions   int32 = 6
+	ADInTicketExtensions          int32 = 7
+	ADMandatoryForKDC             int32 = 8
+	OSFDCE                        int32 = 64
+	SESAME                        int32 = 65
+	ADOSFDCEPKICertID             int32 = 66
+	ADAuthenticationStrength      int32 = 70
+	ADFXFastArmor                 int32 = 71
+	ADFXFastUsed                  int32 = 72
+	ADWin2KPAC                    int32 = 128
+	ADEtypeNegotiation            int32 = 129
+	//Reserved values                   9-63
+)
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/asnAppTag/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/asnAppTag/constants.go
new file mode 100644
index 0000000..d74cd60
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/asnAppTag/constants.go
@@ -0,0 +1,24 @@
+// Package asnAppTag provides ASN1 application tag numbers.
+package asnAppTag
+
+// ASN1 application tag numbers.
+const (
+	Ticket         = 1
+	Authenticator  = 2
+	EncTicketPart  = 3
+	ASREQ          = 10
+	TGSREQ         = 12
+	ASREP          = 11
+	TGSREP         = 13
+	APREQ          = 14
+	APREP          = 15
+	KRBSafe        = 20
+	KRBPriv        = 21
+	KRBCred        = 22
+	EncASRepPart   = 25
+	EncTGSRepPart  = 26
+	EncAPRepPart   = 27
+	EncKrbPrivPart = 28
+	EncKrbCredPart = 29
+	KRBError       = 30
+)
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/chksumtype/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/chksumtype/constants.go
new file mode 100644
index 0000000..93db952
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/chksumtype/constants.go
@@ -0,0 +1,32 @@
+// Package chksumtype provides Kerberos 5 checksum type assigned numbers.
+package chksumtype
+
+// Checksum type IDs.
+const (
+	//RESERVED : 0
+	CRC32         int32 = 1
+	RSA_MD4       int32 = 2
+	RSA_MD4_DES   int32 = 3
+	DES_MAC       int32 = 4
+	DES_MAC_K     int32 = 5
+	RSA_MD4_DES_K int32 = 6
+	RSA_MD5       int32 = 7
+	RSA_MD5_DES   int32 = 8
+	RSA_MD5_DES3  int32 = 9
+	SHA1_ID10     int32 = 10
+	//UNASSIGNED : 11
+	HMAC_SHA1_DES3_KD      int32 = 12
+	HMAC_SHA1_DES3         int32 = 13
+	SHA1_ID14              int32 = 14
+	HMAC_SHA1_96_AES128    int32 = 15
+	HMAC_SHA1_96_AES256    int32 = 16
+	CMAC_CAMELLIA128       int32 = 17
+	CMAC_CAMELLIA256       int32 = 18
+	HMAC_SHA256_128_AES128 int32 = 19
+	HMAC_SHA384_192_AES256 int32 = 20
+	//UNASSIGNED : 21-32770
+	GSSAPI int32 = 32771
+	//UNASSIGNED : 32772-2147483647
+	KERB_CHECKSUM_HMAC_MD5_UNSIGNED uint32 = 4294967158 // 0xFFFFFF76 documentation says this is -138 but in an unsigned int this is 4294967158
+	KERB_CHECKSUM_HMAC_MD5          int32  = -138
+)
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/constants.go
new file mode 100644
index 0000000..0b8e916
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/constants.go
@@ -0,0 +1,5 @@
+// Package iana provides Kerberos 5 assigned numbers.
+package iana
+
+// PVNO is the Protocol Version Number.
+const PVNO = 5
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/errorcode/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/errorcode/constants.go
new file mode 100644
index 0000000..fd756bc
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/errorcode/constants.go
@@ -0,0 +1,155 @@
+// Package errorcode provides Kerberos 5 assigned error codes.
+package errorcode
+
+import "fmt"
+
+// Kerberos error codes.
+const (
+	KDC_ERR_NONE                          int32 = 0  //No error
+	KDC_ERR_NAME_EXP                      int32 = 1  //Client's entry in database has expired
+	KDC_ERR_SERVICE_EXP                   int32 = 2  //Server's entry in database has expired
+	KDC_ERR_BAD_PVNO                      int32 = 3  //Requested protocol version number not supported
+	KDC_ERR_C_OLD_MAST_KVNO               int32 = 4  //Client's key encrypted in old master key
+	KDC_ERR_S_OLD_MAST_KVNO               int32 = 5  //Server's key encrypted in old master key
+	KDC_ERR_C_PRINCIPAL_UNKNOWN           int32 = 6  //Client not found in Kerberos database
+	KDC_ERR_S_PRINCIPAL_UNKNOWN           int32 = 7  //Server not found in Kerberos database
+	KDC_ERR_PRINCIPAL_NOT_UNIQUE          int32 = 8  //Multiple principal entries in database
+	KDC_ERR_NULL_KEY                      int32 = 9  //The client or server has a null key
+	KDC_ERR_CANNOT_POSTDATE               int32 = 10 //Ticket not eligible for  postdating
+	KDC_ERR_NEVER_VALID                   int32 = 11 //Requested starttime is later than end time
+	KDC_ERR_POLICY                        int32 = 12 //KDC policy rejects request
+	KDC_ERR_BADOPTION                     int32 = 13 //KDC cannot accommodate requested option
+	KDC_ERR_ETYPE_NOSUPP                  int32 = 14 //KDC has no support for  encryption type
+	KDC_ERR_SUMTYPE_NOSUPP                int32 = 15 //KDC has no support for  checksum type
+	KDC_ERR_PADATA_TYPE_NOSUPP            int32 = 16 //KDC has no support for  padata type
+	KDC_ERR_TRTYPE_NOSUPP                 int32 = 17 //KDC has no support for  transited type
+	KDC_ERR_CLIENT_REVOKED                int32 = 18 //Clients credentials have been revoked
+	KDC_ERR_SERVICE_REVOKED               int32 = 19 //Credentials for server have been revoked
+	KDC_ERR_TGT_REVOKED                   int32 = 20 //TGT has been revoked
+	KDC_ERR_CLIENT_NOTYET                 int32 = 21 //Client not yet valid; try again later
+	KDC_ERR_SERVICE_NOTYET                int32 = 22 //Server not yet valid; try again later
+	KDC_ERR_KEY_EXPIRED                   int32 = 23 //Password has expired; change password to reset
+	KDC_ERR_PREAUTH_FAILED                int32 = 24 //Pre-authentication information was invalid
+	KDC_ERR_PREAUTH_REQUIRED              int32 = 25 //Additional pre-authentication required
+	KDC_ERR_SERVER_NOMATCH                int32 = 26 //Requested server and ticket don't match
+	KDC_ERR_MUST_USE_USER2USER            int32 = 27 //Server principal valid for  user2user only
+	KDC_ERR_PATH_NOT_ACCEPTED             int32 = 28 //KDC Policy rejects transited path
+	KDC_ERR_SVC_UNAVAILABLE               int32 = 29 //A service is not available
+	KRB_AP_ERR_BAD_INTEGRITY              int32 = 31 //Integrity check on decrypted field failed
+	KRB_AP_ERR_TKT_EXPIRED                int32 = 32 //Ticket expired
+	KRB_AP_ERR_TKT_NYV                    int32 = 33 //Ticket not yet valid
+	KRB_AP_ERR_REPEAT                     int32 = 34 //Request is a replay
+	KRB_AP_ERR_NOT_US                     int32 = 35 //The ticket isn't for us
+	KRB_AP_ERR_BADMATCH                   int32 = 36 //Ticket and authenticator don't match
+	KRB_AP_ERR_SKEW                       int32 = 37 //Clock skew too great
+	KRB_AP_ERR_BADADDR                    int32 = 38 //Incorrect net address
+	KRB_AP_ERR_BADVERSION                 int32 = 39 //Protocol version mismatch
+	KRB_AP_ERR_MSG_TYPE                   int32 = 40 //Invalid msg type
+	KRB_AP_ERR_MODIFIED                   int32 = 41 //Message stream modified
+	KRB_AP_ERR_BADORDER                   int32 = 42 //Message out of order
+	KRB_AP_ERR_BADKEYVER                  int32 = 44 //Specified version of key is not available
+	KRB_AP_ERR_NOKEY                      int32 = 45 //Service key not available
+	KRB_AP_ERR_MUT_FAIL                   int32 = 46 //Mutual authentication failed
+	KRB_AP_ERR_BADDIRECTION               int32 = 47 //Incorrect message direction
+	KRB_AP_ERR_METHOD                     int32 = 48 //Alternative authentication method required
+	KRB_AP_ERR_BADSEQ                     int32 = 49 //Incorrect sequence number in message
+	KRB_AP_ERR_INAPP_CKSUM                int32 = 50 //Inappropriate type of checksum in message
+	KRB_AP_PATH_NOT_ACCEPTED              int32 = 51 //Policy rejects transited path
+	KRB_ERR_RESPONSE_TOO_BIG              int32 = 52 //Response too big for UDP;  retry with TCP
+	KRB_ERR_GENERIC                       int32 = 60 //Generic error (description in e-text)
+	KRB_ERR_FIELD_TOOLONG                 int32 = 61 //Field is too long for this implementation
+	KDC_ERROR_CLIENT_NOT_TRUSTED          int32 = 62 //Reserved for PKINIT
+	KDC_ERROR_KDC_NOT_TRUSTED             int32 = 63 //Reserved for PKINIT
+	KDC_ERROR_INVALID_SIG                 int32 = 64 //Reserved for PKINIT
+	KDC_ERR_KEY_TOO_WEAK                  int32 = 65 //Reserved for PKINIT
+	KDC_ERR_CERTIFICATE_MISMATCH          int32 = 66 //Reserved for PKINIT
+	KRB_AP_ERR_NO_TGT                     int32 = 67 //No TGT available to validate USER-TO-USER
+	KDC_ERR_WRONG_REALM                   int32 = 68 //Reserved for future use
+	KRB_AP_ERR_USER_TO_USER_REQUIRED      int32 = 69 //Ticket must be for  USER-TO-USER
+	KDC_ERR_CANT_VERIFY_CERTIFICATE       int32 = 70 //Reserved for PKINIT
+	KDC_ERR_INVALID_CERTIFICATE           int32 = 71 //Reserved for PKINIT
+	KDC_ERR_REVOKED_CERTIFICATE           int32 = 72 //Reserved for PKINIT
+	KDC_ERR_REVOCATION_STATUS_UNKNOWN     int32 = 73 //Reserved for PKINIT
+	KDC_ERR_REVOCATION_STATUS_UNAVAILABLE int32 = 74 //Reserved for PKINIT
+	KDC_ERR_CLIENT_NAME_MISMATCH          int32 = 75 //Reserved for PKINIT
+	KDC_ERR_KDC_NAME_MISMATCH             int32 = 76 //Reserved for PKINIT
+)
+
+// Lookup an error code description.
+func Lookup(i int32) string {
+	if s, ok := errorcodeLookup[i]; ok {
+		return fmt.Sprintf("(%d) %s", i, s)
+	}
+	return fmt.Sprintf("Unknown ErrorCode %d", i)
+}
+
+var errorcodeLookup = map[int32]string{
+	KDC_ERR_NONE:                          "KDC_ERR_NONE No error",
+	KDC_ERR_NAME_EXP:                      "KDC_ERR_NAME_EXP Client's entry in database has expired",
+	KDC_ERR_SERVICE_EXP:                   "KDC_ERR_SERVICE_EXP Server's entry in database has expired",
+	KDC_ERR_BAD_PVNO:                      "KDC_ERR_BAD_PVNO Requested protocol version number not supported",
+	KDC_ERR_C_OLD_MAST_KVNO:               "KDC_ERR_C_OLD_MAST_KVNO Client's key encrypted in old master key",
+	KDC_ERR_S_OLD_MAST_KVNO:               "KDC_ERR_S_OLD_MAST_KVNO Server's key encrypted in old master key",
+	KDC_ERR_C_PRINCIPAL_UNKNOWN:           "KDC_ERR_C_PRINCIPAL_UNKNOWN Client not found in Kerberos database",
+	KDC_ERR_S_PRINCIPAL_UNKNOWN:           "KDC_ERR_S_PRINCIPAL_UNKNOWN Server not found in Kerberos database",
+	KDC_ERR_PRINCIPAL_NOT_UNIQUE:          "KDC_ERR_PRINCIPAL_NOT_UNIQUE Multiple principal entries in database",
+	KDC_ERR_NULL_KEY:                      "KDC_ERR_NULL_KEY The client or server has a null key",
+	KDC_ERR_CANNOT_POSTDATE:               "KDC_ERR_CANNOT_POSTDATE Ticket not eligible for postdating",
+	KDC_ERR_NEVER_VALID:                   "KDC_ERR_NEVER_VALID Requested starttime is later than end time",
+	KDC_ERR_POLICY:                        "KDC_ERR_POLICY KDC policy rejects request",
+	KDC_ERR_BADOPTION:                     "KDC_ERR_BADOPTION KDC cannot accommodate requested option",
+	KDC_ERR_ETYPE_NOSUPP:                  "KDC_ERR_ETYPE_NOSUPP KDC has no support for encryption type",
+	KDC_ERR_SUMTYPE_NOSUPP:                "KDC_ERR_SUMTYPE_NOSUPP KDC has no support for checksum type",
+	KDC_ERR_PADATA_TYPE_NOSUPP:            "KDC_ERR_PADATA_TYPE_NOSUPP KDC has no support for padata type",
+	KDC_ERR_TRTYPE_NOSUPP:                 "KDC_ERR_TRTYPE_NOSUPP KDC has no support for transited type",
+	KDC_ERR_CLIENT_REVOKED:                "KDC_ERR_CLIENT_REVOKED Clients credentials have been revoked",
+	KDC_ERR_SERVICE_REVOKED:               "KDC_ERR_SERVICE_REVOKED Credentials for server have been revoked",
+	KDC_ERR_TGT_REVOKED:                   "KDC_ERR_TGT_REVOKED TGT has been revoked",
+	KDC_ERR_CLIENT_NOTYET:                 "KDC_ERR_CLIENT_NOTYET Client not yet valid; try again later",
+	KDC_ERR_SERVICE_NOTYET:                "KDC_ERR_SERVICE_NOTYET Server not yet valid; try again later",
+	KDC_ERR_KEY_EXPIRED:                   "KDC_ERR_KEY_EXPIRED Password has expired; change password to reset",
+	KDC_ERR_PREAUTH_FAILED:                "KDC_ERR_PREAUTH_FAILED Pre-authentication information was invalid",
+	KDC_ERR_PREAUTH_REQUIRED:              "KDC_ERR_PREAUTH_REQUIRED Additional pre-authentication required",
+	KDC_ERR_SERVER_NOMATCH:                "KDC_ERR_SERVER_NOMATCH Requested server and ticket don't match",
+	KDC_ERR_MUST_USE_USER2USER:            "KDC_ERR_MUST_USE_USER2USER Server principal valid for  user2user only",
+	KDC_ERR_PATH_NOT_ACCEPTED:             "KDC_ERR_PATH_NOT_ACCEPTED KDC Policy rejects transited path",
+	KDC_ERR_SVC_UNAVAILABLE:               "KDC_ERR_SVC_UNAVAILABLE A service is not available",
+	KRB_AP_ERR_BAD_INTEGRITY:              "KRB_AP_ERR_BAD_INTEGRITY Integrity check on decrypted field failed",
+	KRB_AP_ERR_TKT_EXPIRED:                "KRB_AP_ERR_TKT_EXPIRED Ticket expired",
+	KRB_AP_ERR_TKT_NYV:                    "KRB_AP_ERR_TKT_NYV Ticket not yet valid",
+	KRB_AP_ERR_REPEAT:                     "KRB_AP_ERR_REPEAT Request is a replay",
+	KRB_AP_ERR_NOT_US:                     "KRB_AP_ERR_NOT_US The ticket isn't for us",
+	KRB_AP_ERR_BADMATCH:                   "KRB_AP_ERR_BADMATCH Ticket and authenticator don't match",
+	KRB_AP_ERR_SKEW:                       "KRB_AP_ERR_SKEW Clock skew too great",
+	KRB_AP_ERR_BADADDR:                    "KRB_AP_ERR_BADADDR Incorrect net address",
+	KRB_AP_ERR_BADVERSION:                 "KRB_AP_ERR_BADVERSION Protocol version mismatch",
+	KRB_AP_ERR_MSG_TYPE:                   "KRB_AP_ERR_MSG_TYPE Invalid msg type",
+	KRB_AP_ERR_MODIFIED:                   "KRB_AP_ERR_MODIFIED Message stream modified",
+	KRB_AP_ERR_BADORDER:                   "KRB_AP_ERR_BADORDER Message out of order",
+	KRB_AP_ERR_BADKEYVER:                  "KRB_AP_ERR_BADKEYVER Specified version of key is not available",
+	KRB_AP_ERR_NOKEY:                      "KRB_AP_ERR_NOKEY Service key not available",
+	KRB_AP_ERR_MUT_FAIL:                   "KRB_AP_ERR_MUT_FAIL Mutual authentication failed",
+	KRB_AP_ERR_BADDIRECTION:               "KRB_AP_ERR_BADDIRECTION Incorrect message direction",
+	KRB_AP_ERR_METHOD:                     "KRB_AP_ERR_METHOD Alternative authentication method required",
+	KRB_AP_ERR_BADSEQ:                     "KRB_AP_ERR_BADSEQ Incorrect sequence number in message",
+	KRB_AP_ERR_INAPP_CKSUM:                "KRB_AP_ERR_INAPP_CKSUM Inappropriate type of checksum in message",
+	KRB_AP_PATH_NOT_ACCEPTED:              "KRB_AP_PATH_NOT_ACCEPTED Policy rejects transited path",
+	KRB_ERR_RESPONSE_TOO_BIG:              "KRB_ERR_RESPONSE_TOO_BIG Response too big for UDP; retry with TCP",
+	KRB_ERR_GENERIC:                       "KRB_ERR_GENERIC Generic error (description in e-text)",
+	KRB_ERR_FIELD_TOOLONG:                 "KRB_ERR_FIELD_TOOLONG Field is too long for this implementation",
+	KDC_ERROR_CLIENT_NOT_TRUSTED:          "KDC_ERROR_CLIENT_NOT_TRUSTED Reserved for PKINIT",
+	KDC_ERROR_KDC_NOT_TRUSTED:             "KDC_ERROR_KDC_NOT_TRUSTED Reserved for PKINIT",
+	KDC_ERROR_INVALID_SIG:                 "KDC_ERROR_INVALID_SIG Reserved for PKINIT",
+	KDC_ERR_KEY_TOO_WEAK:                  "KDC_ERR_KEY_TOO_WEAK Reserved for PKINIT",
+	KDC_ERR_CERTIFICATE_MISMATCH:          "KDC_ERR_CERTIFICATE_MISMATCH Reserved for PKINIT",
+	KRB_AP_ERR_NO_TGT:                     "KRB_AP_ERR_NO_TGT No TGT available to validate USER-TO-USER",
+	KDC_ERR_WRONG_REALM:                   "KDC_ERR_WRONG_REALM Reserved for future use",
+	KRB_AP_ERR_USER_TO_USER_REQUIRED:      "KRB_AP_ERR_USER_TO_USER_REQUIRED Ticket must be for USER-TO-USER",
+	KDC_ERR_CANT_VERIFY_CERTIFICATE:       "KDC_ERR_CANT_VERIFY_CERTIFICATE Reserved for PKINIT",
+	KDC_ERR_INVALID_CERTIFICATE:           "KDC_ERR_INVALID_CERTIFICATE Reserved for PKINIT",
+	KDC_ERR_REVOKED_CERTIFICATE:           "KDC_ERR_REVOKED_CERTIFICATE Reserved for PKINIT",
+	KDC_ERR_REVOCATION_STATUS_UNKNOWN:     "KDC_ERR_REVOCATION_STATUS_UNKNOWN Reserved for PKINIT",
+	KDC_ERR_REVOCATION_STATUS_UNAVAILABLE: "KDC_ERR_REVOCATION_STATUS_UNAVAILABLE Reserved for PKINIT",
+	KDC_ERR_CLIENT_NAME_MISMATCH:          "KDC_ERR_CLIENT_NAME_MISMATCH Reserved for PKINIT",
+	KDC_ERR_KDC_NAME_MISMATCH:             "KDC_ERR_KDC_NAME_MISMATCH Reserved for PKINIT",
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/etypeID/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/etypeID/constants.go
new file mode 100644
index 0000000..46a0d74
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/etypeID/constants.go
@@ -0,0 +1,101 @@
+// Package etypeID provides Kerberos 5 encryption type assigned numbers.
+package etypeID
+
+// Kerberos encryption type assigned numbers.
+const (
+	//RESERVED : 0
+	DES_CBC_CRC                  int32 = 1
+	DES_CBC_MD4                  int32 = 2
+	DES_CBC_MD5                  int32 = 3
+	DES_CBC_RAW                  int32 = 4
+	DES3_CBC_MD5                 int32 = 5
+	DES3_CBC_RAW                 int32 = 6
+	DES3_CBC_SHA1                int32 = 7
+	DES_HMAC_SHA1                int32 = 8
+	DSAWITHSHA1_CMSOID           int32 = 9
+	MD5WITHRSAENCRYPTION_CMSOID  int32 = 10
+	SHA1WITHRSAENCRYPTION_CMSOID int32 = 11
+	RC2CBC_ENVOID                int32 = 12
+	RSAENCRYPTION_ENVOID         int32 = 13
+	RSAES_OAEP_ENV_OID           int32 = 14
+	DES_EDE3_CBC_ENV_OID         int32 = 15
+	DES3_CBC_SHA1_KD             int32 = 16
+	AES128_CTS_HMAC_SHA1_96      int32 = 17
+	AES256_CTS_HMAC_SHA1_96      int32 = 18
+	AES128_CTS_HMAC_SHA256_128   int32 = 19
+	AES256_CTS_HMAC_SHA384_192   int32 = 20
+	//UNASSIGNED : 21-22
+	RC4_HMAC             int32 = 23
+	RC4_HMAC_EXP         int32 = 24
+	CAMELLIA128_CTS_CMAC int32 = 25
+	CAMELLIA256_CTS_CMAC int32 = 26
+	//UNASSIGNED : 27-64
+	SUBKEY_KEYMATERIAL int32 = 65
+	//UNASSIGNED : 66-2147483647
+)
+
+// ETypesByName is a map of EncType names to their assigned EncType number.
+var ETypesByName = map[string]int32{
+	"des-cbc-crc":                  DES_CBC_CRC,
+	"des-cbc-md4":                  DES_CBC_MD4,
+	"des-cbc-md5":                  DES_CBC_MD5,
+	"des-cbc-raw":                  DES_CBC_RAW,
+	"des3-cbc-md5":                 DES3_CBC_MD5,
+	"des3-cbc-raw":                 DES3_CBC_RAW,
+	"des3-cbc-sha1":                DES3_CBC_SHA1,
+	"des3-hmac-sha1":               DES_HMAC_SHA1,
+	"des3-cbc-sha1-kd":             DES3_CBC_SHA1_KD,
+	"des-hmac-sha1":                DES_HMAC_SHA1,
+	"dsaWithSHA1-CmsOID":           DSAWITHSHA1_CMSOID,
+	"md5WithRSAEncryption-CmsOID":  MD5WITHRSAENCRYPTION_CMSOID,
+	"sha1WithRSAEncryption-CmsOID": SHA1WITHRSAENCRYPTION_CMSOID,
+	"rc2CBC-EnvOID":                RC2CBC_ENVOID,
+	"rsaEncryption-EnvOID":         RSAENCRYPTION_ENVOID,
+	"rsaES-OAEP-ENV-OID":           RSAES_OAEP_ENV_OID,
+	"des-ede3-cbc-Env-OID":         DES_EDE3_CBC_ENV_OID,
+	"aes128-cts-hmac-sha1-96":      AES128_CTS_HMAC_SHA1_96,
+	"aes128-cts":                   AES128_CTS_HMAC_SHA1_96,
+	"aes128-sha1":                  AES128_CTS_HMAC_SHA1_96,
+	"aes256-cts-hmac-sha1-96":      AES256_CTS_HMAC_SHA1_96,
+	"aes256-cts":                   AES256_CTS_HMAC_SHA1_96,
+	"aes256-sha1":                  AES256_CTS_HMAC_SHA1_96,
+	"aes128-cts-hmac-sha256-128":   AES128_CTS_HMAC_SHA256_128,
+	"aes128-sha2":                  AES128_CTS_HMAC_SHA256_128,
+	"aes256-cts-hmac-sha384-192":   AES256_CTS_HMAC_SHA384_192,
+	"aes256-sha2":                  AES256_CTS_HMAC_SHA384_192,
+	"arcfour-hmac":                 RC4_HMAC,
+	"rc4-hmac":                     RC4_HMAC,
+	"arcfour-hmac-md5":             RC4_HMAC,
+	"arcfour-hmac-exp":             RC4_HMAC_EXP,
+	"rc4-hmac-exp":                 RC4_HMAC_EXP,
+	"arcfour-hmac-md5-exp":         RC4_HMAC_EXP,
+	"camellia128-cts-cmac":         CAMELLIA128_CTS_CMAC,
+	"camellia128-cts":              CAMELLIA128_CTS_CMAC,
+	"camellia256-cts-cmac":         CAMELLIA256_CTS_CMAC,
+	"camellia256-cts":              CAMELLIA256_CTS_CMAC,
+	"subkey-keymaterial":           SUBKEY_KEYMATERIAL,
+}
+
+// EtypeSupported resolves the etype name string to the etype ID.
+// If zero is returned the etype is not supported by gokrb5.
+func EtypeSupported(etype string) int32 {
+	// Slice of supported enctype IDs
+	s := []int32{
+		AES128_CTS_HMAC_SHA1_96,
+		AES256_CTS_HMAC_SHA1_96,
+		AES128_CTS_HMAC_SHA256_128,
+		AES256_CTS_HMAC_SHA384_192,
+		DES3_CBC_SHA1_KD,
+		RC4_HMAC,
+	}
+	id := ETypesByName[etype]
+	if id == 0 {
+		return id
+	}
+	for _, sid := range s {
+		if id == sid {
+			return id
+		}
+	}
+	return 0
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/flags/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/flags/constants.go
new file mode 100644
index 0000000..787801f
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/flags/constants.go
@@ -0,0 +1,36 @@
+// Package flags provides Kerberos 5 flag assigned numbers.
+package flags
+
+// Flag values for KRB5 messages and tickets.
+const (
+	Reserved               = 0
+	Forwardable            = 1
+	Forwarded              = 2
+	Proxiable              = 3
+	Proxy                  = 4
+	AllowPostDate          = 5
+	MayPostDate            = 5
+	PostDated              = 6
+	Invalid                = 7
+	Renewable              = 8
+	Initial                = 9
+	PreAuthent             = 10
+	HWAuthent              = 11
+	OptHardwareAuth        = 11
+	RequestAnonymous       = 12
+	TransitedPolicyChecked = 12
+	OKAsDelegate           = 13
+	EncPARep               = 15
+	Canonicalize           = 15
+	DisableTransitedCheck  = 26
+	RenewableOK            = 27
+	EncTktInSkey           = 28
+	Renew                  = 30
+	Validate               = 31
+
+	// AP Option Flags
+	// 0 Reserved for future use.
+	APOptionUseSessionKey  = 1
+	APOptionMutualRequired = 2
+	// 3-31 Reserved for future use.
+)
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/keyusage/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/keyusage/constants.go
new file mode 100644
index 0000000..5b232d1
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/keyusage/constants.go
@@ -0,0 +1,42 @@
+// Package keyusage provides Kerberos 5 key usage assigned numbers.
+package keyusage
+
+// Key usage numbers.
+const (
+	AS_REQ_PA_ENC_TIMESTAMP                        = 1
+	KDC_REP_TICKET                                 = 2
+	AS_REP_ENCPART                                 = 3
+	TGS_REQ_KDC_REQ_BODY_AUTHDATA_SESSION_KEY      = 4
+	TGS_REQ_KDC_REQ_BODY_AUTHDATA_SUB_KEY          = 5
+	TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM = 6
+	TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR        = 7
+	TGS_REP_ENCPART_SESSION_KEY                    = 8
+	TGS_REP_ENCPART_AUTHENTICATOR_SUB_KEY          = 9
+	AP_REQ_AUTHENTICATOR_CHKSUM                    = 10
+	AP_REQ_AUTHENTICATOR                           = 11
+	AP_REP_ENCPART                                 = 12
+	KRB_PRIV_ENCPART                               = 13
+	KRB_CRED_ENCPART                               = 14
+	KRB_SAFE_CHKSUM                                = 15
+	KERB_NON_KERB_SALT                             = 16
+	KERB_NON_KERB_CKSUM_SALT                       = 17
+	//18.  Reserved for future use in Kerberos and related protocols.
+	AD_KDC_ISSUED_CHKSUM = 19
+	//20-21.  Reserved for future use in Kerberos and related protocols.
+	GSSAPI_ACCEPTOR_SEAL           = 22
+	GSSAPI_ACCEPTOR_SIGN           = 23
+	GSSAPI_INITIATOR_SEAL          = 24
+	GSSAPI_INITIATOR_SIGN          = 25
+	KEY_USAGE_FAST_REQ_CHKSUM      = 50
+	KEY_USAGE_FAST_ENC             = 51
+	KEY_USAGE_FAST_REP             = 52
+	KEY_USAGE_FAST_FINISHED        = 53
+	KEY_USAGE_ENC_CHALLENGE_CLIENT = 54
+	KEY_USAGE_ENC_CHALLENGE_KDC    = 55
+	KEY_USAGE_AS_REQ               = 56
+	//26-511.  Reserved for future use in Kerberos and related protocols.
+	//512-1023.  Reserved for uses internal to a Kerberos implementation.
+	//1024.  Encryption for application use in protocols that do not specify key usage values
+	//1025.  Checksums for application use in protocols that do not specify key usage values
+	//1026-2047.  Reserved for application use.
+)
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/msgtype/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/msgtype/constants.go
new file mode 100644
index 0000000..ad21810
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/msgtype/constants.go
@@ -0,0 +1,18 @@
+// Package msgtype provides Kerberos 5 message type assigned numbers.
+package msgtype
+
+// KRB message type IDs.
+const (
+	KRB_AS_REQ     = 10 //Request for initial authentication
+	KRB_AS_REP     = 11 //Response to KRB_AS_REQ request
+	KRB_TGS_REQ    = 12 //Request for authentication based on TGT
+	KRB_TGS_REP    = 13 //Response to KRB_TGS_REQ request
+	KRB_AP_REQ     = 14 //Application request to server
+	KRB_AP_REP     = 15 //Response to KRB_AP_REQ_MUTUAL
+	KRB_RESERVED16 = 16 //Reserved for user-to-user krb_tgt_request
+	KRB_RESERVED17 = 17 //Reserved for user-to-user krb_tgt_reply
+	KRB_SAFE       = 20 // Safe (checksummed) application message
+	KRB_PRIV       = 21 // Private (encrypted) application message
+	KRB_CRED       = 22 //Private (encrypted) message to forward credentials
+	KRB_ERROR      = 30 //Error response
+)
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/nametype/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/nametype/constants.go
new file mode 100644
index 0000000..c111a05
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/nametype/constants.go
@@ -0,0 +1,15 @@
+// Package nametype provides Kerberos 5 principal name type numbers.
+package nametype
+
+// Kerberos name type IDs.
+const (
+	KRB_NT_UNKNOWN        int32 = 0  //Name type not known
+	KRB_NT_PRINCIPAL      int32 = 1  //Just the name of the principal as in DCE,  or for users
+	KRB_NT_SRV_INST       int32 = 2  //Service and other unique instance (krbtgt)
+	KRB_NT_SRV_HST        int32 = 3  //Service with host name as instance (telnet, rcommands)
+	KRB_NT_SRV_XHST       int32 = 4  //Service with host as remaining components
+	KRB_NT_UID            int32 = 5  //Unique ID
+	KRB_NT_X500_PRINCIPAL int32 = 6  //Encoded X.509 Distinguished name [RFC2253]
+	KRB_NT_SMTP_NAME      int32 = 7  //Name in form of SMTP email name (e.g., user@example.com)
+	KRB_NT_ENTERPRISE     int32 = 10 //Enterprise name; may be mapped to principal name
+)
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/iana/patype/constants.go b/vendor/github.com/jcmturner/gokrb5/v8/iana/patype/constants.go
new file mode 100644
index 0000000..aa04f63
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/iana/patype/constants.go
@@ -0,0 +1,77 @@
+// Package patype provides Kerberos 5 pre-authentication type assigned numbers.
+package patype
+
+// Kerberos pre-authentication type assigned numbers.
+const (
+	PA_TGS_REQ       int32 = 1
+	PA_ENC_TIMESTAMP int32 = 2
+	PA_PW_SALT       int32 = 3
+	//RESERVED : 4
+	PA_ENC_UNIX_TIME       int32 = 5
+	PA_SANDIA_SECUREID     int32 = 6
+	PA_SESAME              int32 = 7
+	PA_OSF_DCE             int32 = 8
+	PA_CYBERSAFE_SECUREID  int32 = 9
+	PA_AFS3_SALT           int32 = 10
+	PA_ETYPE_INFO          int32 = 11
+	PA_SAM_CHALLENGE       int32 = 12
+	PA_SAM_RESPONSE        int32 = 13
+	PA_PK_AS_REQ_OLD       int32 = 14
+	PA_PK_AS_REP_OLD       int32 = 15
+	PA_PK_AS_REQ           int32 = 16
+	PA_PK_AS_REP           int32 = 17
+	PA_PK_OCSP_RESPONSE    int32 = 18
+	PA_ETYPE_INFO2         int32 = 19
+	PA_USE_SPECIFIED_KVNO  int32 = 20
+	PA_SVR_REFERRAL_INFO   int32 = 20
+	PA_SAM_REDIRECT        int32 = 21
+	PA_GET_FROM_TYPED_DATA int32 = 22
+	TD_PADATA              int32 = 22
+	PA_SAM_ETYPE_INFO      int32 = 23
+	PA_ALT_PRINC           int32 = 24
+	PA_SERVER_REFERRAL     int32 = 25
+	//UNASSIGNED : 26-29
+	PA_SAM_CHALLENGE2 int32 = 30
+	PA_SAM_RESPONSE2  int32 = 31
+	//UNASSIGNED : 32-40
+	PA_EXTRA_TGT int32 = 41
+	//UNASSIGNED : 42-100
+	TD_PKINIT_CMS_CERTIFICATES int32 = 101
+	TD_KRB_PRINCIPAL           int32 = 102
+	TD_KRB_REALM               int32 = 103
+	TD_TRUSTED_CERTIFIERS      int32 = 104
+	TD_CERTIFICATE_INDEX       int32 = 105
+	TD_APP_DEFINED_ERROR       int32 = 106
+	TD_REQ_NONCE               int32 = 107
+	TD_REQ_SEQ                 int32 = 108
+	TD_DH_PARAMETERS           int32 = 109
+	//UNASSIGNED : 110
+	TD_CMS_DIGEST_ALGORITHMS  int32 = 111
+	TD_CERT_DIGEST_ALGORITHMS int32 = 112
+	//UNASSIGNED : 113-127
+	PA_PAC_REQUEST         int32 = 128
+	PA_FOR_USER            int32 = 129
+	PA_FOR_X509_USER       int32 = 130
+	PA_FOR_CHECK_DUPS      int32 = 131
+	PA_AS_CHECKSUM         int32 = 132
+	PA_FX_COOKIE           int32 = 133
+	PA_AUTHENTICATION_SET  int32 = 134
+	PA_AUTH_SET_SELECTED   int32 = 135
+	PA_FX_FAST             int32 = 136
+	PA_FX_ERROR            int32 = 137
+	PA_ENCRYPTED_CHALLENGE int32 = 138
+	//UNASSIGNED : 139-140
+	PA_OTP_CHALLENGE  int32 = 141
+	PA_OTP_REQUEST    int32 = 142
+	PA_OTP_CONFIRM    int32 = 143
+	PA_OTP_PIN_CHANGE int32 = 144
+	PA_EPAK_AS_REQ    int32 = 145
+	PA_EPAK_AS_REP    int32 = 146
+	PA_PKINIT_KX      int32 = 147
+	PA_PKU2U_NAME     int32 = 148
+	PA_REQ_ENC_PA_REP int32 = 149
+	PA_AS_FRESHNESS   int32 = 150
+	//UNASSIGNED : 151-164
+	PA_SUPPORTED_ETYPES int32 = 165
+	PA_EXTENDED_ERROR   int32 = 166
+)
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/kadmin/changepasswddata.go b/vendor/github.com/jcmturner/gokrb5/v8/kadmin/changepasswddata.go
new file mode 100644
index 0000000..2d68eda
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/kadmin/changepasswddata.go
@@ -0,0 +1,23 @@
+package kadmin
+
+import (
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// ChangePasswdData is the payload to a password change message.
+type ChangePasswdData struct {
+	NewPasswd []byte              `asn1:"explicit,tag:0"`
+	TargName  types.PrincipalName `asn1:"explicit,optional,tag:1"`
+	TargRealm string              `asn1:"generalstring,optional,explicit,tag:2"`
+}
+
+// Marshal ChangePasswdData into a byte slice.
+func (c *ChangePasswdData) Marshal() ([]byte, error) {
+	b, err := asn1.Marshal(*c)
+	if err != nil {
+		return []byte{}, err
+	}
+	//b = asn1tools.AddASNAppTag(b, asnAppTag.)
+	return b, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/kadmin/message.go b/vendor/github.com/jcmturner/gokrb5/v8/kadmin/message.go
new file mode 100644
index 0000000..d1864c9
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/kadmin/message.go
@@ -0,0 +1,114 @@
+package kadmin
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"math"
+
+	"github.com/jcmturner/gokrb5/v8/messages"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+const (
+	verisonHex = "ff80"
+)
+
+// Request message for changing password.
+type Request struct {
+	APREQ   messages.APReq
+	KRBPriv messages.KRBPriv
+}
+
+// Reply message for a password change.
+type Reply struct {
+	MessageLength int
+	Version       int
+	APREPLength   int
+	APREP         messages.APRep
+	KRBPriv       messages.KRBPriv
+	KRBError      messages.KRBError
+	IsKRBError    bool
+	ResultCode    uint16
+	Result        string
+}
+
+// Marshal a Request into a byte slice.
+func (m *Request) Marshal() (b []byte, err error) {
+	b = []byte{255, 128} // protocol version number: contains the hex constant 0xff80 (big-endian integer).
+	ab, e := m.APREQ.Marshal()
+	if e != nil {
+		err = fmt.Errorf("error marshaling AP_REQ: %v", e)
+		return
+	}
+	if len(ab) > math.MaxUint16 {
+		err = errors.New("length of AP_REQ greater then max Uint16 size")
+		return
+	}
+	al := make([]byte, 2)
+	binary.BigEndian.PutUint16(al, uint16(len(ab)))
+	b = append(b, al...)
+	b = append(b, ab...)
+	pb, e := m.KRBPriv.Marshal()
+	if e != nil {
+		err = fmt.Errorf("error marshaling KRB_Priv: %v", e)
+		return
+	}
+	b = append(b, pb...)
+	if len(b)+2 > math.MaxUint16 {
+		err = errors.New("length of message greater then max Uint16 size")
+		return
+	}
+	ml := make([]byte, 2)
+	binary.BigEndian.PutUint16(ml, uint16(len(b)+2))
+	b = append(ml, b...)
+	return
+}
+
+// Unmarshal a byte slice into a Reply.
+func (m *Reply) Unmarshal(b []byte) error {
+	m.MessageLength = int(binary.BigEndian.Uint16(b[0:2]))
+	m.Version = int(binary.BigEndian.Uint16(b[2:4]))
+	if m.Version != 1 {
+		return fmt.Errorf("kadmin reply has incorrect protocol version number: %d", m.Version)
+	}
+	m.APREPLength = int(binary.BigEndian.Uint16(b[4:6]))
+	if m.APREPLength != 0 {
+		err := m.APREP.Unmarshal(b[6 : 6+m.APREPLength])
+		if err != nil {
+			return err
+		}
+		err = m.KRBPriv.Unmarshal(b[6+m.APREPLength : m.MessageLength])
+		if err != nil {
+			return err
+		}
+	} else {
+		m.IsKRBError = true
+		m.KRBError.Unmarshal(b[6:m.MessageLength])
+		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
+}
+
+// Decrypt the encrypted part of the KRBError within the change password Reply.
+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
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/kadmin/passwd.go b/vendor/github.com/jcmturner/gokrb5/v8/kadmin/passwd.go
new file mode 100644
index 0000000..db199bf
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/kadmin/passwd.go
@@ -0,0 +1,68 @@
+// Package kadmin provides Kerberos administration capabilities.
+package kadmin
+
+import (
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/messages"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// ChangePasswdMsg generate a change password request and also return the key needed to decrypt the reply.
+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),
+		TargName:  cname,
+		TargRealm: realm,
+	}
+	chpwdb, err := chgpasswd.Marshal()
+	if err != nil {
+		err = krberror.Errorf(err, krberror.KRBMsgError, "error marshaling change passwd data")
+		return
+	}
+
+	// Generate authenticator
+	auth, err := types.NewAuthenticator(realm, cname)
+	if err != nil {
+		err = krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
+		return
+	}
+	etype, err := crypto.GetEtype(sessionKey.KeyType)
+	if err != nil {
+		err = krberror.Errorf(err, krberror.KRBMsgError, "error generating subkey etype")
+		return
+	}
+	err = auth.GenerateSeqNumberAndSubKey(etype.GetETypeID(), etype.GetKeyByteSize())
+	if err != nil {
+		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
+	}
+
+	// Form the KRBPriv encpart data
+	kp := messages.EncKrbPrivPart{
+		UserData:       chpwdb,
+		Timestamp:      auth.CTime,
+		Usec:           auth.Cusec,
+		SequenceNumber: auth.SeqNumber,
+	}
+	kpriv := messages.NewKRBPriv(kp)
+	err = kpriv.EncryptEncPart(k)
+	if err != nil {
+		err = krberror.Errorf(err, krberror.EncryptingError, "error encrypting change passwd data")
+		return
+	}
+
+	r = Request{
+		APREQ:   APreq,
+		KRBPriv: kpriv,
+	}
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/keytab/keytab.go b/vendor/github.com/jcmturner/gokrb5/v8/keytab/keytab.go
new file mode 100644
index 0000000..5c2e9d7
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/keytab/keytab.go
@@ -0,0 +1,530 @@
+// Package keytab implements Kerberos keytabs: https://web.mit.edu/kerberos/krb5-devel/doc/formats/keytab_file_format.html.
+package keytab
+
+import (
+	"bytes"
+	"encoding/binary"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"strings"
+	"time"
+	"unsafe"
+
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+const (
+	keytabFirstByte byte = 05
+)
+
+// Keytab struct.
+type Keytab struct {
+	version uint8
+	Entries []entry
+}
+
+// Keytab entry struct.
+type entry struct {
+	Principal principal
+	Timestamp time.Time
+	KVNO8     uint8
+	Key       types.EncryptionKey
+	KVNO      uint32
+}
+
+func (e entry) String() string {
+	return fmt.Sprintf("% 4d %s %-56s %2d %-64x",
+		e.KVNO8,
+		e.Timestamp.Format("02/01/06 15:04:05"),
+		e.Principal.String(),
+		e.Key.KeyType,
+		e.Key.KeyValue,
+	)
+}
+
+// Keytab entry principal struct.
+type principal struct {
+	NumComponents int16 `json:"-"`
+	Realm         string
+	Components    []string
+	NameType      int32
+}
+
+func (p principal) String() string {
+	return fmt.Sprintf("%s@%s", strings.Join(p.Components, "/"), p.Realm)
+}
+
+// New creates new, empty Keytab type.
+func New() *Keytab {
+	var e []entry
+	return &Keytab{
+		version: 2,
+		Entries: e,
+	}
+}
+
+// GetEncryptionKey returns the EncryptionKey from the Keytab for the newest entry with the required kvno, etype and matching principal.
+// If the kvno is zero then the latest kvno will be returned. The kvno is also returned for
+func (kt *Keytab) GetEncryptionKey(princName types.PrincipalName, realm string, kvno int, etype int32) (types.EncryptionKey, int, error) {
+	var key types.EncryptionKey
+	var t time.Time
+	var kv int
+	for _, k := range kt.Entries {
+		if k.Principal.Realm == realm && len(k.Principal.Components) == len(princName.NameString) &&
+			k.Key.KeyType == etype &&
+			(k.KVNO == uint32(kvno) || kvno == 0) &&
+			k.Timestamp.After(t) {
+			p := true
+			for i, n := range k.Principal.Components {
+				if princName.NameString[i] != n {
+					p = false
+					break
+				}
+			}
+			if p {
+				key = k.Key
+				kv = int(k.KVNO)
+				t = k.Timestamp
+			}
+		}
+	}
+	if len(key.KeyValue) < 1 {
+		return key, 0, fmt.Errorf("matching key not found in keytab. Looking for %v realm: %v kvno: %v etype: %v", princName.NameString, realm, kvno, etype)
+	}
+	return key, kv, nil
+}
+
+// Create a new Keytab entry.
+func newEntry() entry {
+	var b []byte
+	return entry{
+		Principal: newPrincipal(),
+		Timestamp: time.Time{},
+		KVNO8:     0,
+		Key: types.EncryptionKey{
+			KeyType:  0,
+			KeyValue: b,
+		},
+		KVNO: 0,
+	}
+}
+
+func (kt Keytab) String() string {
+	var s string
+	s = `KVNO Timestamp         Principal                                                ET Key
+---- ----------------- -------------------------------------------------------- -- ----------------------------------------------------------------
+`
+	for _, entry := range kt.Entries {
+		s += entry.String() + "\n"
+	}
+	return s
+}
+
+// AddEntry adds an entry to the keytab. The password should be provided in plain text and it will be converted using the defined enctype to be stored.
+func (kt *Keytab) AddEntry(principalName, realm, password string, ts time.Time, KVNO uint8, encType int32) error {
+	// Generate a key from the password
+	princ, _ := types.ParseSPNString(principalName)
+	key, _, err := crypto.GetKeyFromPassword(password, princ, realm, encType, types.PADataSequence{})
+	if err != nil {
+		return err
+	}
+
+	// Populate the keytab entry principal
+	ktep := newPrincipal()
+	ktep.NumComponents = int16(len(princ.NameString))
+	if kt.version == 1 {
+		ktep.NumComponents += 1
+	}
+
+	ktep.Realm = realm
+	ktep.Components = princ.NameString
+	ktep.NameType = princ.NameType
+
+	// Populate the keytab entry
+	e := newEntry()
+	e.Principal = ktep
+	e.Timestamp = ts
+	e.KVNO8 = KVNO
+	e.KVNO = uint32(KVNO)
+	e.Key = key
+
+	kt.Entries = append(kt.Entries, e)
+	return nil
+}
+
+// Create a new principal.
+func newPrincipal() principal {
+	var c []string
+	return principal{
+		NumComponents: 0,
+		Realm:         "",
+		Components:    c,
+		NameType:      0,
+	}
+}
+
+// Load a Keytab file into a Keytab type.
+func Load(ktPath string) (*Keytab, error) {
+	kt := new(Keytab)
+	b, err := ioutil.ReadFile(ktPath)
+	if err != nil {
+		return kt, err
+	}
+	err = kt.Unmarshal(b)
+	return kt, err
+}
+
+// Marshal keytab into byte slice
+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
+}
+
+// Write the keytab bytes to io.Writer.
+// Returns the number of bytes written
+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)
+}
+
+// Unmarshal byte slice of Keytab data into Keytab type.
+func (kt *Keytab) Unmarshal(b []byte) error {
+	if len(b) < 2 {
+		return fmt.Errorf("byte array is less than 2 bytes: %d", len(b))
+	}
+
+	//The first byte of the file always has the value 5
+	if b[0] != keytabFirstByte {
+		return errors.New("invalid keytab data. First byte does not equal 5")
+	}
+	//Get keytab version
+	//The 2nd byte contains the version number (1 or 2)
+	kt.version = b[1]
+	if kt.version != 1 && kt.version != 2 {
+		return errors.New("invalid keytab data. Keytab version is neither 1 nor 2")
+	}
+	//Version 1 of the file format uses native byte order for integer representations. Version 2 always uses big-endian byte order
+	var endian binary.ByteOrder
+	endian = binary.BigEndian
+	if kt.version == 1 && isNativeEndianLittle() {
+		endian = binary.LittleEndian
+	}
+	// n tracks position in the byte array
+	n := 2
+	l, err := readInt32(b, &n, &endian)
+	if err != nil {
+		return err
+	}
+	for l != 0 {
+		if l < 0 {
+			//Zero padded so skip over
+			l = l * -1
+			n = n + int(l)
+		} else {
+			if n < 0 {
+				return fmt.Errorf("%d can't be less than zero", n)
+			}
+			if n+int(l) > len(b) {
+				return fmt.Errorf("%s's length is less than %d", b, n+int(l))
+			}
+			eb := b[n : n+int(l)]
+			n = n + int(l)
+			ke := newEntry()
+			// p keeps track as to where we are in the byte stream
+			var p int
+			var err error
+			parsePrincipal(eb, &p, kt, &ke, &endian)
+			ke.Timestamp, err = readTimestamp(eb, &p, &endian)
+			if err != nil {
+				return err
+			}
+			rei8, err := readInt8(eb, &p, &endian)
+			if err != nil {
+				return err
+			}
+			ke.KVNO8 = uint8(rei8)
+			rei16, err := readInt16(eb, &p, &endian)
+			if err != nil {
+				return err
+			}
+			ke.Key.KeyType = int32(rei16)
+			rei16, err = readInt16(eb, &p, &endian)
+			if err != nil {
+				return err
+			}
+			kl := int(rei16)
+			ke.Key.KeyValue, err = readBytes(eb, &p, kl, &endian)
+			if err != nil {
+				return err
+			}
+			// The 32-bit key version overrides the 8-bit key version.
+			// If at least 4 bytes are left after the other fields are read and they are non-zero
+			// this indicates the 32-bit version is present.
+			if len(eb)-p >= 4 {
+				// The 32-bit key may be present
+				ri32, err := readInt32(eb, &p, &endian)
+				if err != nil {
+					return err
+				}
+				ke.KVNO = uint32(ri32)
+			}
+			if ke.KVNO == 0 {
+				// Handles if the value from the last 4 bytes was zero and also if there are not the 4 bytes present. Makes sense to put the same value here as KVNO8
+				ke.KVNO = uint32(ke.KVNO8)
+			}
+			// Add the entry to the keytab
+			kt.Entries = append(kt.Entries, ke)
+		}
+		// Check if there are still 4 bytes left to read
+		// Also check that n is greater than zero
+		if n < 0 || n > len(b) || len(b[n:]) < 4 {
+			break
+		}
+		// Read the size of the next entry
+		l, err = readInt32(b, &n, &endian)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+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] = 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) error {
+	var err error
+	ke.Principal.NumComponents, err = readInt16(b, p, e)
+	if err != nil {
+		return err
+	}
+	if kt.version == 1 {
+		//In version 1 the number of components includes the realm. Minus 1 to make consistent with version 2
+		ke.Principal.NumComponents--
+	}
+	lenRealm, err := readInt16(b, p, e)
+	if err != nil {
+		return err
+	}
+	realmB, err := readBytes(b, p, int(lenRealm), e)
+	if err != nil {
+		return err
+	}
+	ke.Principal.Realm = string(realmB)
+	for i := 0; i < int(ke.Principal.NumComponents); i++ {
+		l, err := readInt16(b, p, e)
+		if err != nil {
+			return err
+		}
+		compB, err := readBytes(b, p, int(l), e)
+		if err != nil {
+			return err
+		}
+		ke.Principal.Components = append(ke.Principal.Components, string(compB))
+	}
+	if kt.version != 1 {
+		//Name Type is omitted in version 1
+		ke.Principal.NameType, err = readInt32(b, p, e)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+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, error) {
+	i32, err := readInt32(b, p, e)
+	if err != nil {
+		return time.Time{}, err
+	}
+	return time.Unix(int64(i32), 0), nil
+}
+
+// Read bytes representing an eight bit integer.
+func readInt8(b []byte, p *int, e *binary.ByteOrder) (i int8, err error) {
+	if *p < 0 {
+		return 0, fmt.Errorf("%d cannot be less than zero", *p)
+	}
+
+	if (*p + 1) > len(b) {
+		return 0, fmt.Errorf("%s's length is less than %d", b, *p+1)
+	}
+	buf := bytes.NewBuffer(b[*p : *p+1])
+	binary.Read(buf, *e, &i)
+	*p++
+	return
+}
+
+// Read bytes representing a sixteen bit integer.
+func readInt16(b []byte, p *int, e *binary.ByteOrder) (i int16, err error) {
+	if *p < 0 {
+		return 0, fmt.Errorf("%d cannot be less than zero", *p)
+	}
+
+	if (*p + 2) > len(b) {
+		return 0, fmt.Errorf("%s's length is less than %d", b, *p+2)
+	}
+
+	buf := bytes.NewBuffer(b[*p : *p+2])
+	binary.Read(buf, *e, &i)
+	*p += 2
+	return
+}
+
+// Read bytes representing a thirty two bit integer.
+func readInt32(b []byte, p *int, e *binary.ByteOrder) (i int32, err error) {
+	if *p < 0 {
+		return 0, fmt.Errorf("%d cannot be less than zero", *p)
+	}
+
+	if (*p + 4) > len(b) {
+		return 0, fmt.Errorf("%s's length is less than %d", b, *p+4)
+	}
+
+	buf := bytes.NewBuffer(b[*p : *p+4])
+	binary.Read(buf, *e, &i)
+	*p += 4
+	return
+}
+
+func readBytes(b []byte, p *int, s int, e *binary.ByteOrder) ([]byte, error) {
+	if s < 0 {
+		return nil, fmt.Errorf("%d cannot be less than zero", s)
+	}
+	i := *p + s
+	if i > len(b) {
+		return nil, fmt.Errorf("%s's length is greater than %d", b, i)
+	}
+	buf := bytes.NewBuffer(b[*p:i])
+	r := make([]byte, s)
+	if err := binary.Read(buf, *e, &r); err != nil {
+		return nil, err
+	}
+	*p += s
+	return r, nil
+}
+
+func isNativeEndianLittle() bool {
+	var x = 0x012345678
+	var p = unsafe.Pointer(&x)
+	var bp = (*[4]byte)(p)
+
+	var endian bool
+	if 0x01 == bp[0] {
+		endian = false
+	} else if (0x78 & 0xff) == (bp[0] & 0xff) {
+		endian = true
+	} else {
+		// Default to big endian
+		endian = false
+	}
+	return endian
+}
+
+// JSON return information about the keys held in the keytab in a JSON format.
+func (kt *Keytab) JSON() (string, error) {
+	b, err := json.MarshalIndent(kt, "", "  ")
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/krberror/error.go b/vendor/github.com/jcmturner/gokrb5/v8/krberror/error.go
new file mode 100644
index 0000000..01c6d99
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/krberror/error.go
@@ -0,0 +1,67 @@
+// Package krberror provides error type and functions for gokrb5.
+package krberror
+
+import (
+	"fmt"
+	"strings"
+)
+
+// Error type descriptions.
+const (
+	separator       = " < "
+	EncodingError   = "Encoding_Error"
+	NetworkingError = "Networking_Error"
+	DecryptingError = "Decrypting_Error"
+	EncryptingError = "Encrypting_Error"
+	ChksumError     = "Checksum_Error"
+	KRBMsgError     = "KRBMessage_Handling_Error"
+	ConfigError     = "Configuration_Error"
+	KDCError        = "KDC_Error"
+)
+
+// Krberror is an error type for gokrb5
+type Krberror struct {
+	RootCause string
+	EText     []string
+}
+
+// Error function to implement the error interface.
+func (e Krberror) Error() string {
+	return fmt.Sprintf("[Root cause: %s] ", e.RootCause) + strings.Join(e.EText, separator)
+}
+
+// Add another error statement to the error.
+func (e *Krberror) Add(et string, s string) {
+	e.EText = append([]string{fmt.Sprintf("%s: %s", et, s)}, e.EText...)
+}
+
+// New creates a new instance of Krberror.
+func New(et, s string) Krberror {
+	return Krberror{
+		RootCause: et,
+		EText:     []string{s},
+	}
+}
+
+// Errorf appends to or creates a new Krberror.
+func Errorf(err error, et, format string, a ...interface{}) Krberror {
+	if e, ok := err.(Krberror); ok {
+		e.Add(et, fmt.Sprintf(format, a...))
+		return e
+	}
+	return NewErrorf(et, format+": %s", append(a, err)...)
+}
+
+// NewErrorf creates a new Krberror from a formatted string.
+func NewErrorf(et, format string, a ...interface{}) Krberror {
+	var s string
+	if len(a) > 0 {
+		s = fmt.Sprintf("%s: %s", et, fmt.Sprintf(format, a...))
+	} else {
+		s = fmt.Sprintf("%s: %s", et, format)
+	}
+	return Krberror{
+		RootCause: et,
+		EText:     []string{s},
+	}
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/messages/APRep.go b/vendor/github.com/jcmturner/gokrb5/v8/messages/APRep.go
new file mode 100644
index 0000000..555fb80
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/messages/APRep.go
@@ -0,0 +1,49 @@
+package messages
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// APRep implements RFC 4120 KRB_AP_REP: https://tools.ietf.org/html/rfc4120#section-5.5.2.
+type APRep struct {
+	PVNO    int                 `asn1:"explicit,tag:0"`
+	MsgType int                 `asn1:"explicit,tag:1"`
+	EncPart types.EncryptedData `asn1:"explicit,tag:2"`
+}
+
+// EncAPRepPart is the encrypted part of KRB_AP_REP.
+type EncAPRepPart struct {
+	CTime          time.Time           `asn1:"generalized,explicit,tag:0"`
+	Cusec          int                 `asn1:"explicit,tag:1"`
+	Subkey         types.EncryptionKey `asn1:"optional,explicit,tag:2"`
+	SequenceNumber int64               `asn1:"optional,explicit,tag:3"`
+}
+
+// Unmarshal bytes b into the APRep struct.
+func (a *APRep) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, a, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.APREP))
+	if err != nil {
+		return processUnmarshalReplyError(b, err)
+	}
+	expectedMsgType := msgtype.KRB_AP_REP
+	if a.MsgType != expectedMsgType {
+		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_AP_REP. Expected: %v; Actual: %v", expectedMsgType, a.MsgType)
+	}
+	return nil
+}
+
+// Unmarshal bytes b into the APRep encrypted part struct.
+func (a *EncAPRepPart) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, a, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncAPRepPart))
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "AP_REP unmarshal error")
+	}
+	return nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/messages/APReq.go b/vendor/github.com/jcmturner/gokrb5/v8/messages/APReq.go
new file mode 100644
index 0000000..1836079
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/messages/APReq.go
@@ -0,0 +1,199 @@
+package messages
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/asn1tools"
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/v8/iana/errorcode"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
+	"github.com/jcmturner/gokrb5/v8/keytab"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+type marshalAPReq struct {
+	PVNO      int            `asn1:"explicit,tag:0"`
+	MsgType   int            `asn1:"explicit,tag:1"`
+	APOptions asn1.BitString `asn1:"explicit,tag:2"`
+	// Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
+	Ticket                 asn1.RawValue       `asn1:"explicit,tag:3"`
+	EncryptedAuthenticator types.EncryptedData `asn1:"explicit,tag:4"`
+}
+
+// APReq implements RFC 4120 KRB_AP_REQ: https://tools.ietf.org/html/rfc4120#section-5.5.1.
+type APReq struct {
+	PVNO                   int                 `asn1:"explicit,tag:0"`
+	MsgType                int                 `asn1:"explicit,tag:1"`
+	APOptions              asn1.BitString      `asn1:"explicit,tag:2"`
+	Ticket                 Ticket              `asn1:"explicit,tag:3"`
+	EncryptedAuthenticator types.EncryptedData `asn1:"explicit,tag:4"`
+	Authenticator          types.Authenticator `asn1:"optional"`
+}
+
+// NewAPReq generates a new KRB_AP_REQ struct.
+func NewAPReq(tkt Ticket, sessionKey types.EncryptionKey, auth types.Authenticator) (APReq, error) {
+	var a APReq
+	ed, err := encryptAuthenticator(auth, sessionKey, tkt)
+	if err != nil {
+		return a, krberror.Errorf(err, krberror.KRBMsgError, "error creating Authenticator for AP_REQ")
+	}
+	a = APReq{
+		PVNO:                   iana.PVNO,
+		MsgType:                msgtype.KRB_AP_REQ,
+		APOptions:              types.NewKrbFlags(),
+		Ticket:                 tkt,
+		EncryptedAuthenticator: ed,
+	}
+	return a, nil
+}
+
+// Encrypt Authenticator
+func encryptAuthenticator(a types.Authenticator, sessionKey types.EncryptionKey, tkt Ticket) (types.EncryptedData, error) {
+	var ed types.EncryptedData
+	m, err := a.Marshal()
+	if err != nil {
+		return ed, krberror.Errorf(err, krberror.EncodingError, "marshaling error of EncryptedData form of Authenticator")
+	}
+	usage := authenticatorKeyUsage(tkt.SName)
+	ed, err = crypto.GetEncryptedData(m, sessionKey, uint32(usage), tkt.EncPart.KVNO)
+	if err != nil {
+		return ed, krberror.Errorf(err, krberror.EncryptingError, "error encrypting Authenticator")
+	}
+	return ed, nil
+}
+
+// DecryptAuthenticator decrypts the Authenticator within the AP_REQ.
+// sessionKey may simply be the key within the decrypted EncPart of the ticket within the AP_REQ.
+func (a *APReq) DecryptAuthenticator(sessionKey types.EncryptionKey) error {
+	usage := authenticatorKeyUsage(a.Ticket.SName)
+	ab, e := crypto.DecryptEncPart(a.EncryptedAuthenticator, sessionKey, uint32(usage))
+	if e != nil {
+		return fmt.Errorf("error decrypting authenticator: %v", e)
+	}
+	err := a.Authenticator.Unmarshal(ab)
+	if err != nil {
+		return fmt.Errorf("error unmarshaling authenticator: %v", err)
+	}
+	return nil
+}
+
+func authenticatorKeyUsage(pn types.PrincipalName) int {
+	if pn.NameString[0] == "krbtgt" {
+		return keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR
+	}
+	return keyusage.AP_REQ_AUTHENTICATOR
+}
+
+// Unmarshal bytes b into the APReq struct.
+func (a *APReq) Unmarshal(b []byte) error {
+	var m marshalAPReq
+	_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.APREQ))
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "unmarshal error of AP_REQ")
+	}
+	if m.MsgType != msgtype.KRB_AP_REQ {
+		return NewKRBError(types.PrincipalName{}, "", errorcode.KRB_AP_ERR_MSG_TYPE, errorcode.Lookup(errorcode.KRB_AP_ERR_MSG_TYPE))
+	}
+	a.PVNO = m.PVNO
+	a.MsgType = m.MsgType
+	a.APOptions = m.APOptions
+	a.EncryptedAuthenticator = m.EncryptedAuthenticator
+	a.Ticket, err = unmarshalTicket(m.Ticket.Bytes)
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "unmarshaling error of Ticket within AP_REQ")
+	}
+	return nil
+}
+
+// Marshal APReq struct.
+func (a *APReq) Marshal() ([]byte, error) {
+	m := marshalAPReq{
+		PVNO:                   a.PVNO,
+		MsgType:                a.MsgType,
+		APOptions:              a.APOptions,
+		EncryptedAuthenticator: a.EncryptedAuthenticator,
+	}
+	var b []byte
+	b, err := a.Ticket.Marshal()
+	if err != nil {
+		return b, err
+	}
+	m.Ticket = asn1.RawValue{
+		Class:      asn1.ClassContextSpecific,
+		IsCompound: true,
+		Tag:        3,
+		Bytes:      b,
+	}
+	mk, err := asn1.Marshal(m)
+	if err != nil {
+		return mk, krberror.Errorf(err, krberror.EncodingError, "marshaling error of AP_REQ")
+	}
+	mk = asn1tools.AddASNAppTag(mk, asnAppTag.APREQ)
+	return mk, nil
+}
+
+// Verify an AP_REQ using service's keytab, spn and max acceptable clock skew duration.
+// The service ticket encrypted part and authenticator will be decrypted as part of this operation.
+func (a *APReq) Verify(kt *keytab.Keytab, d time.Duration, cAddr types.HostAddress, snameOverride *types.PrincipalName) (bool, error) {
+	// Decrypt ticket's encrypted part with service key
+	//TODO decrypt with service's session key from its TGT is use-to-user. Need to figure out how to get TGT.
+	//if types.IsFlagSet(&a.APOptions, flags.APOptionUseSessionKey) {
+	//	err := a.Ticket.Decrypt(tgt.DecryptedEncPart.Key)
+	//	if err != nil {
+	//		return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of ticket provided using session key")
+	//	}
+	//} else {
+	//	err := a.Ticket.DecryptEncPart(*kt, &a.Ticket.SName)
+	//	if err != nil {
+	//		return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided")
+	//	}
+	//}
+	sname := &a.Ticket.SName
+	if snameOverride != nil {
+		sname = snameOverride
+	}
+	err := a.Ticket.DecryptEncPart(kt, sname)
+	if err != nil {
+		return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided")
+	}
+
+	// Check time validity of ticket
+	ok, err := a.Ticket.Valid(d)
+	if err != nil || !ok {
+		return ok, err
+	}
+
+	// Check client's address is listed in the client addresses in the ticket
+	if len(a.Ticket.DecryptedEncPart.CAddr) > 0 {
+		//If client addresses are present check if any of them match the source IP that sent the APReq
+		//If there is no match return KRB_AP_ERR_BADADDR error.
+		if !types.HostAddressesContains(a.Ticket.DecryptedEncPart.CAddr, cAddr) {
+			return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "client address not within the list contained in the service ticket")
+		}
+	}
+
+	// Decrypt authenticator with session key from ticket's encrypted part
+	err = a.DecryptAuthenticator(a.Ticket.DecryptedEncPart.Key)
+	if err != nil {
+		return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BAD_INTEGRITY, "could not decrypt authenticator")
+	}
+
+	// Check CName in authenticator is the same as that in the ticket
+	if !a.Authenticator.CName.Equal(a.Ticket.DecryptedEncPart.CName) {
+		return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_BADMATCH, "CName in Authenticator does not match that in service ticket")
+	}
+
+	// Check the clock skew between the client and the service server
+	ct := a.Authenticator.CTime.Add(time.Duration(a.Authenticator.Cusec) * time.Microsecond)
+	t := time.Now().UTC()
+	if t.Sub(ct) > d || ct.Sub(t) > d {
+		return false, NewKRBError(a.Ticket.SName, a.Ticket.Realm, errorcode.KRB_AP_ERR_SKEW, fmt.Sprintf("clock skew with client too large. greater than %v seconds", d))
+	}
+	return true, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/messages/KDCRep.go b/vendor/github.com/jcmturner/gokrb5/v8/messages/KDCRep.go
new file mode 100644
index 0000000..69df9f0
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/messages/KDCRep.go
@@ -0,0 +1,360 @@
+package messages
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.4.2
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/asn1tools"
+	"github.com/jcmturner/gokrb5/v8/config"
+	"github.com/jcmturner/gokrb5/v8/credentials"
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/v8/iana/flags"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
+	"github.com/jcmturner/gokrb5/v8/iana/patype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+type marshalKDCRep struct {
+	PVNO    int                  `asn1:"explicit,tag:0"`
+	MsgType int                  `asn1:"explicit,tag:1"`
+	PAData  types.PADataSequence `asn1:"explicit,optional,tag:2"`
+	CRealm  string               `asn1:"generalstring,explicit,tag:3"`
+	CName   types.PrincipalName  `asn1:"explicit,tag:4"`
+	// Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
+	Ticket  asn1.RawValue       `asn1:"explicit,tag:5"`
+	EncPart types.EncryptedData `asn1:"explicit,tag:6"`
+}
+
+// KDCRepFields represents the KRB_KDC_REP fields.
+type KDCRepFields struct {
+	PVNO             int
+	MsgType          int
+	PAData           []types.PAData
+	CRealm           string
+	CName            types.PrincipalName
+	Ticket           Ticket
+	EncPart          types.EncryptedData
+	DecryptedEncPart EncKDCRepPart
+}
+
+// ASRep implements RFC 4120 KRB_AS_REP: https://tools.ietf.org/html/rfc4120#section-5.4.2.
+type ASRep struct {
+	KDCRepFields
+}
+
+// TGSRep implements RFC 4120 KRB_TGS_REP: https://tools.ietf.org/html/rfc4120#section-5.4.2.
+type TGSRep struct {
+	KDCRepFields
+}
+
+// EncKDCRepPart is the encrypted part of KRB_KDC_REP.
+type EncKDCRepPart struct {
+	Key           types.EncryptionKey  `asn1:"explicit,tag:0"`
+	LastReqs      []LastReq            `asn1:"explicit,tag:1"`
+	Nonce         int                  `asn1:"explicit,tag:2"`
+	KeyExpiration time.Time            `asn1:"generalized,explicit,optional,tag:3"`
+	Flags         asn1.BitString       `asn1:"explicit,tag:4"`
+	AuthTime      time.Time            `asn1:"generalized,explicit,tag:5"`
+	StartTime     time.Time            `asn1:"generalized,explicit,optional,tag:6"`
+	EndTime       time.Time            `asn1:"generalized,explicit,tag:7"`
+	RenewTill     time.Time            `asn1:"generalized,explicit,optional,tag:8"`
+	SRealm        string               `asn1:"generalstring,explicit,tag:9"`
+	SName         types.PrincipalName  `asn1:"explicit,tag:10"`
+	CAddr         []types.HostAddress  `asn1:"explicit,optional,tag:11"`
+	EncPAData     types.PADataSequence `asn1:"explicit,optional,tag:12"`
+}
+
+// LastReq part of KRB_KDC_REP.
+type LastReq struct {
+	LRType  int32     `asn1:"explicit,tag:0"`
+	LRValue time.Time `asn1:"generalized,explicit,tag:1"`
+}
+
+// Unmarshal bytes b into the ASRep struct.
+func (k *ASRep) Unmarshal(b []byte) error {
+	var m marshalKDCRep
+	_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREP))
+	if err != nil {
+		return processUnmarshalReplyError(b, err)
+	}
+	if m.MsgType != msgtype.KRB_AS_REP {
+		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an AS_REP. Expected: %v; Actual: %v", msgtype.KRB_AS_REP, m.MsgType)
+	}
+	//Process the raw ticket within
+	tkt, err := unmarshalTicket(m.Ticket.Bytes)
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within AS_REP")
+	}
+	k.KDCRepFields = KDCRepFields{
+		PVNO:    m.PVNO,
+		MsgType: m.MsgType,
+		PAData:  m.PAData,
+		CRealm:  m.CRealm,
+		CName:   m.CName,
+		Ticket:  tkt,
+		EncPart: m.EncPart,
+	}
+	return nil
+}
+
+// Marshal ASRep struct.
+func (k *ASRep) Marshal() ([]byte, error) {
+	m := marshalKDCRep{
+		PVNO:    k.PVNO,
+		MsgType: k.MsgType,
+		PAData:  k.PAData,
+		CRealm:  k.CRealm,
+		CName:   k.CName,
+		EncPart: k.EncPart,
+	}
+	b, err := k.Ticket.Marshal()
+	if err != nil {
+		return []byte{}, err
+	}
+	m.Ticket = asn1.RawValue{
+		Class:      asn1.ClassContextSpecific,
+		IsCompound: true,
+		Tag:        5,
+		Bytes:      b,
+	}
+	mk, err := asn1.Marshal(m)
+	if err != nil {
+		return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REP")
+	}
+	mk = asn1tools.AddASNAppTag(mk, asnAppTag.ASREP)
+	return mk, nil
+}
+
+// Unmarshal bytes b into the TGSRep struct.
+func (k *TGSRep) Unmarshal(b []byte) error {
+	var m marshalKDCRep
+	_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREP))
+	if err != nil {
+		return processUnmarshalReplyError(b, err)
+	}
+	if m.MsgType != msgtype.KRB_TGS_REP {
+		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate an TGS_REP. Expected: %v; Actual: %v", msgtype.KRB_TGS_REP, m.MsgType)
+	}
+	//Process the raw ticket within
+	tkt, err := unmarshalTicket(m.Ticket.Bytes)
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling Ticket within TGS_REP")
+	}
+	k.KDCRepFields = KDCRepFields{
+		PVNO:    m.PVNO,
+		MsgType: m.MsgType,
+		PAData:  m.PAData,
+		CRealm:  m.CRealm,
+		CName:   m.CName,
+		Ticket:  tkt,
+		EncPart: m.EncPart,
+	}
+	return nil
+}
+
+// Marshal TGSRep struct.
+func (k *TGSRep) Marshal() ([]byte, error) {
+	m := marshalKDCRep{
+		PVNO:    k.PVNO,
+		MsgType: k.MsgType,
+		PAData:  k.PAData,
+		CRealm:  k.CRealm,
+		CName:   k.CName,
+		EncPart: k.EncPart,
+	}
+	b, err := k.Ticket.Marshal()
+	if err != nil {
+		return []byte{}, err
+	}
+	m.Ticket = asn1.RawValue{
+		Class:      asn1.ClassContextSpecific,
+		IsCompound: true,
+		Tag:        5,
+		Bytes:      b,
+	}
+	mk, err := asn1.Marshal(m)
+	if err != nil {
+		return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling TGS_REP")
+	}
+	mk = asn1tools.AddASNAppTag(mk, asnAppTag.TGSREP)
+	return mk, nil
+}
+
+// Unmarshal bytes b into encrypted part of KRB_KDC_REP.
+func (e *EncKDCRepPart) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, e, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncASRepPart))
+	if err != nil {
+		// Try using tag 26
+		// Ref: RFC 4120 - mentions that some implementations use application tag number 26 wether or not the reply is
+		// a AS-REP or a TGS-REP.
+		_, err = asn1.UnmarshalWithParams(b, e, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncTGSRepPart))
+		if err != nil {
+			return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part within KDC_REP")
+		}
+	}
+	return nil
+}
+
+// Marshal encrypted part of KRB_KDC_REP.
+func (e *EncKDCRepPart) Marshal() ([]byte, error) {
+	b, err := asn1.Marshal(*e)
+	if err != nil {
+		return b, krberror.Errorf(err, krberror.EncodingError, "marshaling error of AS_REP encpart")
+	}
+	b = asn1tools.AddASNAppTag(b, asnAppTag.EncASRepPart)
+	return b, nil
+}
+
+// DecryptEncPart decrypts the encrypted part of an AS_REP.
+func (k *ASRep) DecryptEncPart(c *credentials.Credentials) (types.EncryptionKey, error) {
+	var key types.EncryptionKey
+	var err error
+	if c.HasKeytab() {
+		key, _, err = c.Keytab().GetEncryptionKey(k.CName, k.CRealm, k.EncPart.KVNO, k.EncPart.EType)
+		if err != nil {
+			return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part")
+		}
+	}
+	if c.HasPassword() {
+		key, _, err = crypto.GetKeyFromPassword(c.Password(), k.CName, k.CRealm, k.EncPart.EType, k.PAData)
+		if err != nil {
+			return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part")
+		}
+	}
+	if !c.HasKeytab() && !c.HasPassword() {
+		return key, krberror.NewErrorf(krberror.DecryptingError, "no secret available in credentials to perform decryption of AS_REP encrypted part")
+	}
+	b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.AS_REP_ENCPART)
+	if err != nil {
+		return key, krberror.Errorf(err, krberror.DecryptingError, "error decrypting AS_REP encrypted part")
+	}
+	var denc EncKDCRepPart
+	err = denc.Unmarshal(b)
+	if err != nil {
+		return key, krberror.Errorf(err, krberror.EncodingError, "error unmarshaling decrypted encpart of AS_REP")
+	}
+	k.DecryptedEncPart = denc
+	return key, nil
+}
+
+// Verify checks the validity of AS_REP message.
+func (k *ASRep) Verify(cfg *config.Config, creds *credentials.Credentials, asReq ASReq) (bool, error) {
+	//Ref RFC 4120 Section 3.1.5
+	if !k.CName.Equal(asReq.ReqBody.CName) {
+		return false, krberror.NewErrorf(krberror.KRBMsgError, "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, krberror.NewErrorf(krberror.KRBMsgError, "CRealm in response does not match what was requested. Requested: %s; Reply: %s", asReq.ReqBody.Realm, k.CRealm)
+	}
+	key, err := k.DecryptEncPart(creds)
+	if err != nil {
+		return false, krberror.Errorf(err, krberror.DecryptingError, "error decrypting EncPart of AS_REP")
+	}
+	if k.DecryptedEncPart.Nonce != asReq.ReqBody.Nonce {
+		return false, krberror.NewErrorf(krberror.KRBMsgError, "possible replay attack, nonce in response does not match that in request")
+	}
+	if !k.DecryptedEncPart.SName.Equal(asReq.ReqBody.SName) {
+		return false, krberror.NewErrorf(krberror.KRBMsgError, "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, krberror.NewErrorf(krberror.KRBMsgError, "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 {
+		if !types.HostAddressesEqual(k.DecryptedEncPart.CAddr, asReq.ReqBody.Addresses) {
+			return false, krberror.NewErrorf(krberror.KRBMsgError, "addresses listed in the AS_REP does not match those listed in the AS_REQ")
+		}
+	}
+	t := time.Now().UTC()
+	if t.Sub(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.AuthTime.Sub(t) > cfg.LibDefaults.Clockskew {
+		return false, krberror.NewErrorf(krberror.KRBMsgError, "clock skew with KDC too large. Greater than %v seconds", cfg.LibDefaults.Clockskew.Seconds())
+	}
+	// RFC 6806 https://tools.ietf.org/html/rfc6806.html#section-11
+	if asReq.PAData.Contains(patype.PA_REQ_ENC_PA_REP) && types.IsFlagSet(&k.DecryptedEncPart.Flags, flags.EncPARep) {
+		if len(k.DecryptedEncPart.EncPAData) < 2 || !k.DecryptedEncPart.EncPAData.Contains(patype.PA_FX_FAST) {
+			return false, krberror.NewErrorf(krberror.KRBMsgError, "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, krberror.Errorf(err, krberror.EncodingError, "KDC FAST negotiation response error, could not unmarshal PA_REQ_ENC_PA_REP")
+				}
+				etype, err := crypto.GetChksumEtype(pafast.ChksumType)
+				if err != nil {
+					return false, krberror.Errorf(err, krberror.ChksumError, "KDC FAST negotiation response error")
+				}
+				ab, _ := asReq.Marshal()
+				if !etype.VerifyChecksum(key.KeyValue, ab, pafast.Chksum, keyusage.KEY_USAGE_AS_REQ) {
+					return false, krberror.Errorf(err, krberror.ChksumError, "KDC FAST negotiation response checksum invalid")
+				}
+			}
+		}
+	}
+	return true, nil
+}
+
+// DecryptEncPart decrypts the encrypted part of an TGS_REP.
+func (k *TGSRep) DecryptEncPart(key types.EncryptionKey) error {
+	b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.TGS_REP_ENCPART_SESSION_KEY)
+	if err != nil {
+		return krberror.Errorf(err, krberror.DecryptingError, "error decrypting TGS_REP EncPart")
+	}
+	var denc EncKDCRepPart
+	err = denc.Unmarshal(b)
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part")
+	}
+	k.DecryptedEncPart = denc
+	return nil
+}
+
+// Verify checks the validity of the TGS_REP message.
+func (k *TGSRep) Verify(cfg *config.Config, tgsReq TGSReq) (bool, error) {
+	if !k.CName.Equal(tgsReq.ReqBody.CName) {
+		return false, krberror.NewErrorf(krberror.KRBMsgError, "CName in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.CName, k.CName)
+	}
+	if k.Ticket.Realm != tgsReq.ReqBody.Realm {
+		return false, krberror.NewErrorf(krberror.KRBMsgError, "realm in response ticket does not match what was requested. Requested: %s; Reply: %s", tgsReq.ReqBody.Realm, k.Ticket.Realm)
+	}
+	if k.DecryptedEncPart.Nonce != tgsReq.ReqBody.Nonce {
+		return false, krberror.NewErrorf(krberror.KRBMsgError, "possible replay attack, nonce in response does not match that in request")
+	}
+	//if k.Ticket.SName.NameType != tgsReq.ReqBody.SName.NameType || k.Ticket.SName.NameString == nil {
+	//	return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response ticket does not match what was requested. Requested: %v; Reply: %v", tgsReq.ReqBody.SName, k.Ticket.SName)
+	//}
+	//for i := range k.Ticket.SName.NameString {
+	//	if k.Ticket.SName.NameString[i] != tgsReq.ReqBody.SName.NameString[i] {
+	//		return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response ticket does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.SName, k.Ticket.SName)
+	//	}
+	//}
+	//if k.DecryptedEncPart.SName.NameType != tgsReq.ReqBody.SName.NameType || k.DecryptedEncPart.SName.NameString == nil {
+	//	return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %v; Reply: %v", tgsReq.ReqBody.SName, k.DecryptedEncPart.SName)
+	//}
+	//for i := range k.DecryptedEncPart.SName.NameString {
+	//	if k.DecryptedEncPart.SName.NameString[i] != tgsReq.ReqBody.SName.NameString[i] {
+	//		return false, krberror.NewErrorf(krberror.KRBMsgError, "SName in response does not match what was requested. Requested: %+v; Reply: %+v", tgsReq.ReqBody.SName, k.DecryptedEncPart.SName)
+	//	}
+	//}
+	if k.DecryptedEncPart.SRealm != tgsReq.ReqBody.Realm {
+		return false, krberror.NewErrorf(krberror.KRBMsgError, "SRealm in response does not match what was requested. Requested: %s; Reply: %s", tgsReq.ReqBody.Realm, k.DecryptedEncPart.SRealm)
+	}
+	if len(k.DecryptedEncPart.CAddr) > 0 {
+		if !types.HostAddressesEqual(k.DecryptedEncPart.CAddr, tgsReq.ReqBody.Addresses) {
+			return false, krberror.NewErrorf(krberror.KRBMsgError, "addresses listed in the TGS_REP does not match those listed in the TGS_REQ")
+		}
+	}
+	if time.Since(k.DecryptedEncPart.StartTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.StartTime.Sub(time.Now().UTC()) > cfg.LibDefaults.Clockskew {
+		if time.Since(k.DecryptedEncPart.AuthTime) > cfg.LibDefaults.Clockskew || k.DecryptedEncPart.AuthTime.Sub(time.Now().UTC()) > cfg.LibDefaults.Clockskew {
+			return false, krberror.NewErrorf(krberror.KRBMsgError, "clock skew with KDC too large. Greater than %v seconds.", cfg.LibDefaults.Clockskew.Seconds())
+		}
+	}
+	return true, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/messages/KDCReq.go b/vendor/github.com/jcmturner/gokrb5/v8/messages/KDCReq.go
new file mode 100644
index 0000000..3745afe
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/messages/KDCReq.go
@@ -0,0 +1,432 @@
+package messages
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.4.1
+
+import (
+	"crypto/rand"
+	"fmt"
+	"math"
+	"math/big"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/asn1tools"
+	"github.com/jcmturner/gokrb5/v8/config"
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/v8/iana/flags"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
+	"github.com/jcmturner/gokrb5/v8/iana/nametype"
+	"github.com/jcmturner/gokrb5/v8/iana/patype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+type marshalKDCReq struct {
+	PVNO    int                  `asn1:"explicit,tag:1"`
+	MsgType int                  `asn1:"explicit,tag:2"`
+	PAData  types.PADataSequence `asn1:"explicit,optional,tag:3"`
+	ReqBody asn1.RawValue        `asn1:"explicit,tag:4"`
+}
+
+// KDCReqFields represents the KRB_KDC_REQ fields.
+type KDCReqFields struct {
+	PVNO    int
+	MsgType int
+	PAData  types.PADataSequence
+	ReqBody KDCReqBody
+	Renewal bool
+}
+
+// ASReq implements RFC 4120 KRB_AS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1.
+type ASReq struct {
+	KDCReqFields
+}
+
+// TGSReq implements RFC 4120 KRB_TGS_REQ: https://tools.ietf.org/html/rfc4120#section-5.4.1.
+type TGSReq struct {
+	KDCReqFields
+}
+
+type marshalKDCReqBody struct {
+	KDCOptions  asn1.BitString      `asn1:"explicit,tag:0"`
+	CName       types.PrincipalName `asn1:"explicit,optional,tag:1"`
+	Realm       string              `asn1:"generalstring,explicit,tag:2"`
+	SName       types.PrincipalName `asn1:"explicit,optional,tag:3"`
+	From        time.Time           `asn1:"generalized,explicit,optional,tag:4"`
+	Till        time.Time           `asn1:"generalized,explicit,tag:5"`
+	RTime       time.Time           `asn1:"generalized,explicit,optional,tag:6"`
+	Nonce       int                 `asn1:"explicit,tag:7"`
+	EType       []int32             `asn1:"explicit,tag:8"`
+	Addresses   []types.HostAddress `asn1:"explicit,optional,tag:9"`
+	EncAuthData types.EncryptedData `asn1:"explicit,optional,tag:10"`
+	// Ticket needs to be a raw value as it is wrapped in an APPLICATION tag
+	AdditionalTickets asn1.RawValue `asn1:"explicit,optional,tag:11"`
+}
+
+// KDCReqBody implements the KRB_KDC_REQ request body.
+type KDCReqBody struct {
+	KDCOptions        asn1.BitString      `asn1:"explicit,tag:0"`
+	CName             types.PrincipalName `asn1:"explicit,optional,tag:1"`
+	Realm             string              `asn1:"generalstring,explicit,tag:2"`
+	SName             types.PrincipalName `asn1:"explicit,optional,tag:3"`
+	From              time.Time           `asn1:"generalized,explicit,optional,tag:4"`
+	Till              time.Time           `asn1:"generalized,explicit,tag:5"`
+	RTime             time.Time           `asn1:"generalized,explicit,optional,tag:6"`
+	Nonce             int                 `asn1:"explicit,tag:7"`
+	EType             []int32             `asn1:"explicit,tag:8"`
+	Addresses         []types.HostAddress `asn1:"explicit,optional,tag:9"`
+	EncAuthData       types.EncryptedData `asn1:"explicit,optional,tag:10"`
+	AdditionalTickets []Ticket            `asn1:"explicit,optional,tag:11"`
+}
+
+// NewASReqForTGT generates a new KRB_AS_REQ struct for a TGT request.
+func NewASReqForTGT(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) {
+	sname := types.PrincipalName{
+		NameType:   nametype.KRB_NT_SRV_INST,
+		NameString: []string{"krbtgt", realm},
+	}
+	return NewASReq(realm, c, cname, sname)
+}
+
+// NewASReqForChgPasswd generates a new KRB_AS_REQ struct for a change password request.
+func NewASReqForChgPasswd(realm string, c *config.Config, cname types.PrincipalName) (ASReq, error) {
+	sname := types.PrincipalName{
+		NameType:   nametype.KRB_NT_PRINCIPAL,
+		NameString: []string{"kadmin", "changepw"},
+	}
+	return NewASReq(realm, c, cname, sname)
+}
+
+// NewASReq generates a new KRB_AS_REQ struct for a given SNAME.
+func NewASReq(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
+	}
+	t := time.Now().UTC()
+	// Copy the default options to make this thread safe
+	kopts := types.NewKrbFlags()
+	copy(kopts.Bytes, c.LibDefaults.KDCDefaultOptions.Bytes)
+	kopts.BitLength = c.LibDefaults.KDCDefaultOptions.BitLength
+	a := ASReq{
+		KDCReqFields{
+			PVNO:    iana.PVNO,
+			MsgType: msgtype.KRB_AS_REQ,
+			PAData:  types.PADataSequence{},
+			ReqBody: KDCReqBody{
+				KDCOptions: kopts,
+				Realm:      realm,
+				CName:      cname,
+				SName:      sname,
+				Till:       t.Add(c.LibDefaults.TicketLifetime),
+				Nonce:      int(nonce.Int64()),
+				EType:      c.LibDefaults.DefaultTktEnctypeIDs,
+			},
+		},
+	}
+	if c.LibDefaults.Forwardable {
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Forwardable)
+	}
+	if c.LibDefaults.Canonicalize {
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Canonicalize)
+	}
+	if c.LibDefaults.Proxiable {
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Proxiable)
+	}
+	if c.LibDefaults.RenewLifetime != 0 {
+		types.SetFlag(&a.ReqBody.KDCOptions, flags.Renewable)
+		a.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime)
+		a.ReqBody.RTime = t.Add(time.Duration(48) * time.Hour)
+	}
+	if !c.LibDefaults.NoAddresses {
+		ha, err := types.LocalHostAddresses()
+		if err != nil {
+			return a, fmt.Errorf("could not get local addresses: %v", err)
+		}
+		ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...)
+		a.ReqBody.Addresses = ha
+	}
+	return a, nil
+}
+
+// NewTGSReq generates a new KRB_TGS_REQ struct.
+func NewTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, tgt Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool) (TGSReq, error) {
+	a, err := tgsReq(cname, sname, kdcRealm, renewal, c)
+	if err != nil {
+		return a, err
+	}
+	err = a.setPAData(tgt, sessionKey)
+	return a, err
+}
+
+// NewUser2UserTGSReq returns a TGS-REQ suitable for user-to-user authentication (https://tools.ietf.org/html/rfc4120#section-3.7)
+func NewUser2UserTGSReq(cname types.PrincipalName, kdcRealm string, c *config.Config, clientTGT Ticket, sessionKey types.EncryptionKey, sname types.PrincipalName, renewal bool, verifyingTGT Ticket) (TGSReq, error) {
+	a, err := tgsReq(cname, sname, kdcRealm, renewal, c)
+	if err != nil {
+		return a, err
+	}
+	a.ReqBody.AdditionalTickets = []Ticket{verifyingTGT}
+	types.SetFlag(&a.ReqBody.KDCOptions, flags.EncTktInSkey)
+	err = a.setPAData(clientTGT, sessionKey)
+	return a, err
+}
+
+// tgsReq populates the fields for a TGS_REQ
+func tgsReq(cname, sname types.PrincipalName, kdcRealm string, renewal bool, c *config.Config) (TGSReq, error) {
+	nonce, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
+	if err != nil {
+		return TGSReq{}, err
+	}
+	t := time.Now().UTC()
+	k := KDCReqFields{
+		PVNO:    iana.PVNO,
+		MsgType: msgtype.KRB_TGS_REQ,
+		ReqBody: KDCReqBody{
+			KDCOptions: types.NewKrbFlags(),
+			Realm:      kdcRealm,
+			CName:      cname, // Add the CName to make validation of the reply easier
+			SName:      sname,
+			Till:       t.Add(c.LibDefaults.TicketLifetime),
+			Nonce:      int(nonce.Int64()),
+			EType:      c.LibDefaults.DefaultTGSEnctypeIDs,
+		},
+		Renewal: renewal,
+	}
+	if c.LibDefaults.Forwardable {
+		types.SetFlag(&k.ReqBody.KDCOptions, flags.Forwardable)
+	}
+	if c.LibDefaults.Canonicalize {
+		types.SetFlag(&k.ReqBody.KDCOptions, flags.Canonicalize)
+	}
+	if c.LibDefaults.Proxiable {
+		types.SetFlag(&k.ReqBody.KDCOptions, flags.Proxiable)
+	}
+	if c.LibDefaults.RenewLifetime > time.Duration(0) {
+		types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable)
+		k.ReqBody.RTime = t.Add(c.LibDefaults.RenewLifetime)
+	}
+	if !c.LibDefaults.NoAddresses {
+		ha, err := types.LocalHostAddresses()
+		if err != nil {
+			return TGSReq{}, fmt.Errorf("could not get local addresses: %v", err)
+		}
+		ha = append(ha, types.HostAddressesFromNetIPs(c.LibDefaults.ExtraAddresses)...)
+		k.ReqBody.Addresses = ha
+	}
+	if renewal {
+		types.SetFlag(&k.ReqBody.KDCOptions, flags.Renew)
+		types.SetFlag(&k.ReqBody.KDCOptions, flags.Renewable)
+	}
+	return TGSReq{
+		k,
+	}, nil
+}
+
+func (k *TGSReq) setPAData(tgt Ticket, sessionKey types.EncryptionKey) error {
+	// Marshal the request and calculate checksum
+	b, err := k.ReqBody.Marshal()
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error marshaling TGS_REQ body")
+	}
+	etype, err := crypto.GetEtype(sessionKey.KeyType)
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncryptingError, "error getting etype to encrypt authenticator")
+	}
+	cb, err := etype.GetChecksumHash(sessionKey.KeyValue, b, keyusage.TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM)
+	if err != nil {
+		return krberror.Errorf(err, krberror.ChksumError, "error getting etype checksum hash")
+	}
+
+	// Form PAData for TGS_REQ
+	// Create authenticator
+	auth, err := types.NewAuthenticator(tgt.Realm, k.ReqBody.CName)
+	if err != nil {
+		return krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
+	}
+	auth.Cksum = types.Checksum{
+		CksumType: etype.GetHashID(),
+		Checksum:  cb,
+	}
+	// Create AP_REQ
+	apReq, err := NewAPReq(tgt, sessionKey, auth)
+	if err != nil {
+		return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AP_REQ")
+	}
+	apb, err := apReq.Marshal()
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error marshaling AP_REQ for pre-authentication data")
+	}
+	k.PAData = types.PADataSequence{
+		types.PAData{
+			PADataType:  patype.PA_TGS_REQ,
+			PADataValue: apb,
+		},
+	}
+	return nil
+}
+
+// Unmarshal bytes b into the ASReq struct.
+func (k *ASReq) Unmarshal(b []byte) error {
+	var m marshalKDCReq
+	_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.ASREQ))
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling AS_REQ")
+	}
+	expectedMsgType := msgtype.KRB_AS_REQ
+	if m.MsgType != expectedMsgType {
+		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a AS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType)
+	}
+	var reqb KDCReqBody
+	err = reqb.Unmarshal(m.ReqBody.Bytes)
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error processing AS_REQ body")
+	}
+	k.MsgType = m.MsgType
+	k.PAData = m.PAData
+	k.PVNO = m.PVNO
+	k.ReqBody = reqb
+	return nil
+}
+
+// Unmarshal bytes b into the TGSReq struct.
+func (k *TGSReq) Unmarshal(b []byte) error {
+	var m marshalKDCReq
+	_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.TGSREQ))
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling TGS_REQ")
+	}
+	expectedMsgType := msgtype.KRB_TGS_REQ
+	if m.MsgType != expectedMsgType {
+		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a TGS_REQ. Expected: %v; Actual: %v", expectedMsgType, m.MsgType)
+	}
+	var reqb KDCReqBody
+	err = reqb.Unmarshal(m.ReqBody.Bytes)
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error processing TGS_REQ body")
+	}
+	k.MsgType = m.MsgType
+	k.PAData = m.PAData
+	k.PVNO = m.PVNO
+	k.ReqBody = reqb
+	return nil
+}
+
+// Unmarshal bytes b into the KRB_KDC_REQ body struct.
+func (k *KDCReqBody) Unmarshal(b []byte) error {
+	var m marshalKDCReqBody
+	_, err := asn1.Unmarshal(b, &m)
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling KDC_REQ body")
+	}
+	k.KDCOptions = m.KDCOptions
+	if len(k.KDCOptions.Bytes) < 4 {
+		tb := make([]byte, 4-len(k.KDCOptions.Bytes))
+		k.KDCOptions.Bytes = append(tb, k.KDCOptions.Bytes...)
+		k.KDCOptions.BitLength = len(k.KDCOptions.Bytes) * 8
+	}
+	k.CName = m.CName
+	k.Realm = m.Realm
+	k.SName = m.SName
+	k.From = m.From
+	k.Till = m.Till
+	k.RTime = m.RTime
+	k.Nonce = m.Nonce
+	k.EType = m.EType
+	k.Addresses = m.Addresses
+	k.EncAuthData = m.EncAuthData
+	if len(m.AdditionalTickets.Bytes) > 0 {
+		k.AdditionalTickets, err = unmarshalTicketsSequence(m.AdditionalTickets)
+		if err != nil {
+			return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling additional tickets")
+		}
+	}
+	return nil
+}
+
+// Marshal ASReq struct.
+func (k *ASReq) Marshal() ([]byte, error) {
+	m := marshalKDCReq{
+		PVNO:    k.PVNO,
+		MsgType: k.MsgType,
+		PAData:  k.PAData,
+	}
+	b, err := k.ReqBody.Marshal()
+	if err != nil {
+		var mk []byte
+		return mk, err
+	}
+	m.ReqBody = asn1.RawValue{
+		Class:      asn1.ClassContextSpecific,
+		IsCompound: true,
+		Tag:        4,
+		Bytes:      b,
+	}
+	mk, err := asn1.Marshal(m)
+	if err != nil {
+		return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ")
+	}
+	mk = asn1tools.AddASNAppTag(mk, asnAppTag.ASREQ)
+	return mk, nil
+}
+
+// Marshal TGSReq struct.
+func (k *TGSReq) Marshal() ([]byte, error) {
+	m := marshalKDCReq{
+		PVNO:    k.PVNO,
+		MsgType: k.MsgType,
+		PAData:  k.PAData,
+	}
+	b, err := k.ReqBody.Marshal()
+	if err != nil {
+		var mk []byte
+		return mk, err
+	}
+	m.ReqBody = asn1.RawValue{
+		Class:      asn1.ClassContextSpecific,
+		IsCompound: true,
+		Tag:        4,
+		Bytes:      b,
+	}
+	mk, err := asn1.Marshal(m)
+	if err != nil {
+		return mk, krberror.Errorf(err, krberror.EncodingError, "error marshaling AS_REQ")
+	}
+	mk = asn1tools.AddASNAppTag(mk, asnAppTag.TGSREQ)
+	return mk, nil
+}
+
+// Marshal KRB_KDC_REQ body struct.
+func (k *KDCReqBody) Marshal() ([]byte, error) {
+	var b []byte
+	m := marshalKDCReqBody{
+		KDCOptions:  k.KDCOptions,
+		CName:       k.CName,
+		Realm:       k.Realm,
+		SName:       k.SName,
+		From:        k.From,
+		Till:        k.Till,
+		RTime:       k.RTime,
+		Nonce:       k.Nonce,
+		EType:       k.EType,
+		Addresses:   k.Addresses,
+		EncAuthData: k.EncAuthData,
+	}
+	rawtkts, err := MarshalTicketSequence(k.AdditionalTickets)
+	if err != nil {
+		return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body additional tickets")
+	}
+	//The asn1.rawValue needs the tag setting on it for where it is in the KDCReqBody
+	rawtkts.Tag = 11
+	if len(rawtkts.Bytes) > 0 {
+		m.AdditionalTickets = rawtkts
+	}
+	b, err = asn1.Marshal(m)
+	if err != nil {
+		return b, krberror.Errorf(err, krberror.EncodingError, "error in marshaling KDC request body")
+	}
+	return b, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBCred.go b/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBCred.go
new file mode 100644
index 0000000..536fdb9
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBCred.go
@@ -0,0 +1,102 @@
+package messages
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+type marshalKRBCred struct {
+	PVNO    int                 `asn1:"explicit,tag:0"`
+	MsgType int                 `asn1:"explicit,tag:1"`
+	Tickets asn1.RawValue       `asn1:"explicit,tag:2"`
+	EncPart types.EncryptedData `asn1:"explicit,tag:3"`
+}
+
+// KRBCred implements RFC 4120 KRB_CRED: https://tools.ietf.org/html/rfc4120#section-5.8.1.
+type KRBCred struct {
+	PVNO             int
+	MsgType          int
+	Tickets          []Ticket
+	EncPart          types.EncryptedData
+	DecryptedEncPart EncKrbCredPart
+}
+
+// EncKrbCredPart is the encrypted part of KRB_CRED.
+type EncKrbCredPart struct {
+	TicketInfo []KrbCredInfo     `asn1:"explicit,tag:0"`
+	Nouce      int               `asn1:"optional,explicit,tag:1"`
+	Timestamp  time.Time         `asn1:"generalized,optional,explicit,tag:2"`
+	Usec       int               `asn1:"optional,explicit,tag:3"`
+	SAddress   types.HostAddress `asn1:"optional,explicit,tag:4"`
+	RAddress   types.HostAddress `asn1:"optional,explicit,tag:5"`
+}
+
+// KrbCredInfo is the KRB_CRED_INFO part of KRB_CRED.
+type KrbCredInfo struct {
+	Key       types.EncryptionKey `asn1:"explicit,tag:0"`
+	PRealm    string              `asn1:"generalstring,optional,explicit,tag:1"`
+	PName     types.PrincipalName `asn1:"optional,explicit,tag:2"`
+	Flags     asn1.BitString      `asn1:"optional,explicit,tag:3"`
+	AuthTime  time.Time           `asn1:"generalized,optional,explicit,tag:4"`
+	StartTime time.Time           `asn1:"generalized,optional,explicit,tag:5"`
+	EndTime   time.Time           `asn1:"generalized,optional,explicit,tag:6"`
+	RenewTill time.Time           `asn1:"generalized,optional,explicit,tag:7"`
+	SRealm    string              `asn1:"optional,explicit,ia5,tag:8"`
+	SName     types.PrincipalName `asn1:"optional,explicit,tag:9"`
+	CAddr     types.HostAddresses `asn1:"optional,explicit,tag:10"`
+}
+
+// Unmarshal bytes b into the KRBCred struct.
+func (k *KRBCred) Unmarshal(b []byte) error {
+	var m marshalKRBCred
+	_, err := asn1.UnmarshalWithParams(b, &m, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBCred))
+	if err != nil {
+		return processUnmarshalReplyError(b, err)
+	}
+	expectedMsgType := msgtype.KRB_CRED
+	if m.MsgType != expectedMsgType {
+		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_CRED. Expected: %v; Actual: %v", expectedMsgType, m.MsgType)
+	}
+	k.PVNO = m.PVNO
+	k.MsgType = m.MsgType
+	k.EncPart = m.EncPart
+	if len(m.Tickets.Bytes) > 0 {
+		k.Tickets, err = unmarshalTicketsSequence(m.Tickets)
+		if err != nil {
+			return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling tickets within KRB_CRED")
+		}
+	}
+	return nil
+}
+
+// DecryptEncPart decrypts the encrypted part of a KRB_CRED.
+func (k *KRBCred) DecryptEncPart(key types.EncryptionKey) error {
+	b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.KRB_CRED_ENCPART)
+	if err != nil {
+		return krberror.Errorf(err, krberror.DecryptingError, "error decrypting KRB_CRED EncPart")
+	}
+	var denc EncKrbCredPart
+	err = denc.Unmarshal(b)
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling encrypted part of KRB_CRED")
+	}
+	k.DecryptedEncPart = denc
+	return nil
+}
+
+// Unmarshal bytes b into the encrypted part of KRB_CRED.
+func (k *EncKrbCredPart) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncKrbCredPart))
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "error unmarshaling EncKrbCredPart")
+	}
+	return nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBError.go b/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBError.go
new file mode 100644
index 0000000..d2cf32d
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBError.go
@@ -0,0 +1,94 @@
+// Package messages implements Kerberos 5 message types and methods.
+package messages
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/asn1tools"
+	"github.com/jcmturner/gokrb5/v8/iana"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/v8/iana/errorcode"
+	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// KRBError implements RFC 4120 KRB_ERROR: https://tools.ietf.org/html/rfc4120#section-5.9.1.
+type KRBError struct {
+	PVNO      int                 `asn1:"explicit,tag:0"`
+	MsgType   int                 `asn1:"explicit,tag:1"`
+	CTime     time.Time           `asn1:"generalized,optional,explicit,tag:2"`
+	Cusec     int                 `asn1:"optional,explicit,tag:3"`
+	STime     time.Time           `asn1:"generalized,explicit,tag:4"`
+	Susec     int                 `asn1:"explicit,tag:5"`
+	ErrorCode int32               `asn1:"explicit,tag:6"`
+	CRealm    string              `asn1:"generalstring,optional,explicit,tag:7"`
+	CName     types.PrincipalName `asn1:"optional,explicit,tag:8"`
+	Realm     string              `asn1:"generalstring,explicit,tag:9"`
+	SName     types.PrincipalName `asn1:"explicit,tag:10"`
+	EText     string              `asn1:"generalstring,optional,explicit,tag:11"`
+	EData     []byte              `asn1:"optional,explicit,tag:12"`
+}
+
+// NewKRBError creates a new KRBError.
+func NewKRBError(sname types.PrincipalName, realm string, code int32, etext string) KRBError {
+	t := time.Now().UTC()
+	return KRBError{
+		PVNO:      iana.PVNO,
+		MsgType:   msgtype.KRB_ERROR,
+		STime:     t,
+		Susec:     int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)),
+		ErrorCode: code,
+		SName:     sname,
+		Realm:     realm,
+		EText:     etext,
+	}
+}
+
+// Unmarshal bytes b into the KRBError struct.
+func (k *KRBError) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBError))
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "KRB_ERROR unmarshal error")
+	}
+	expectedMsgType := msgtype.KRB_ERROR
+	if k.MsgType != expectedMsgType {
+		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_ERROR. Expected: %v; Actual: %v", expectedMsgType, k.MsgType)
+	}
+	return nil
+}
+
+// Marshal a KRBError into bytes.
+func (k *KRBError) Marshal() ([]byte, error) {
+	b, err := asn1.Marshal(*k)
+	if err != nil {
+		return b, krberror.Errorf(err, krberror.EncodingError, "error marshaling KRBError")
+	}
+	b = asn1tools.AddASNAppTag(b, asnAppTag.KRBError)
+	return b, nil
+}
+
+// Error method implementing error interface on KRBError struct.
+func (k KRBError) Error() string {
+	etxt := fmt.Sprintf("KRB Error: %s", errorcode.Lookup(k.ErrorCode))
+	if k.EText != "" {
+		etxt = fmt.Sprintf("%s - %s", etxt, k.EText)
+	}
+	return etxt
+}
+
+func processUnmarshalReplyError(b []byte, err error) error {
+	switch err.(type) {
+	case asn1.StructuralError:
+		var krberr KRBError
+		tmperr := krberr.Unmarshal(b)
+		if tmperr != nil {
+			return krberror.Errorf(err, krberror.EncodingError, "failed to unmarshal KDC's reply")
+		}
+		return krberr
+	default:
+		return krberror.Errorf(err, krberror.EncodingError, "failed to unmarshal KDC's reply")
+	}
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBPriv.go b/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBPriv.go
new file mode 100644
index 0000000..0ca6149
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBPriv.go
@@ -0,0 +1,108 @@
+package messages
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/asn1tools"
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// KRBPriv implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.7.1.
+type KRBPriv struct {
+	PVNO             int                 `asn1:"explicit,tag:0"`
+	MsgType          int                 `asn1:"explicit,tag:1"`
+	EncPart          types.EncryptedData `asn1:"explicit,tag:3"`
+	DecryptedEncPart EncKrbPrivPart      `asn1:"optional,omitempty"` // Not part of ASN1 bytes so marked as optional so unmarshalling works
+}
+
+// EncKrbPrivPart is the encrypted part of KRB_PRIV.
+type EncKrbPrivPart struct {
+	UserData       []byte            `asn1:"explicit,tag:0"`
+	Timestamp      time.Time         `asn1:"generalized,optional,explicit,tag:1"`
+	Usec           int               `asn1:"optional,explicit,tag:2"`
+	SequenceNumber int64             `asn1:"optional,explicit,tag:3"`
+	SAddress       types.HostAddress `asn1:"explicit,tag:4"`
+	RAddress       types.HostAddress `asn1:"optional,explicit,tag:5"`
+}
+
+// NewKRBPriv returns a new KRBPriv type.
+func NewKRBPriv(part EncKrbPrivPart) KRBPriv {
+	return KRBPriv{
+		PVNO:             iana.PVNO,
+		MsgType:          msgtype.KRB_PRIV,
+		DecryptedEncPart: part,
+	}
+}
+
+// Unmarshal bytes b into the KRBPriv struct.
+func (k *KRBPriv) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBPriv))
+	if err != nil {
+		return processUnmarshalReplyError(b, err)
+	}
+	expectedMsgType := msgtype.KRB_PRIV
+	if k.MsgType != expectedMsgType {
+		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_PRIV. Expected: %v; Actual: %v", expectedMsgType, k.MsgType)
+	}
+	return nil
+}
+
+// Unmarshal bytes b into the EncKrbPrivPart struct.
+func (k *EncKrbPrivPart) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, k, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.EncKrbPrivPart))
+	if err != nil {
+		return krberror.Errorf(err, krberror.EncodingError, "KRB_PRIV unmarshal error")
+	}
+	return nil
+}
+
+// Marshal the KRBPriv.
+func (k *KRBPriv) Marshal() ([]byte, error) {
+	tk := KRBPriv{
+		PVNO:    k.PVNO,
+		MsgType: k.MsgType,
+		EncPart: k.EncPart,
+	}
+	b, err := asn1.Marshal(tk)
+	if err != nil {
+		return []byte{}, err
+	}
+	b = asn1tools.AddASNAppTag(b, asnAppTag.KRBPriv)
+	return b, nil
+}
+
+// EncryptEncPart encrypts the DecryptedEncPart within the KRBPriv.
+// Use to prepare for marshaling.
+func (k *KRBPriv) EncryptEncPart(key types.EncryptionKey) error {
+	b, err := asn1.Marshal(k.DecryptedEncPart)
+	if err != nil {
+		return err
+	}
+	b = asn1tools.AddASNAppTag(b, asnAppTag.EncKrbPrivPart)
+	k.EncPart, err = crypto.GetEncryptedData(b, key, keyusage.KRB_PRIV_ENCPART, 1)
+	if err != nil {
+		return err
+	}
+	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
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBSafe.go b/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBSafe.go
new file mode 100644
index 0000000..52cd284
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/messages/KRBSafe.go
@@ -0,0 +1,43 @@
+package messages
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/v8/iana/msgtype"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// KRBSafe implements RFC 4120 KRB_SAFE: https://tools.ietf.org/html/rfc4120#section-5.6.1.
+type KRBSafe struct {
+	PVNO     int            `asn1:"explicit,tag:0"`
+	MsgType  int            `asn1:"explicit,tag:1"`
+	SafeBody KRBSafeBody    `asn1:"explicit,tag:2"`
+	Cksum    types.Checksum `asn1:"explicit,tag:3"`
+}
+
+// KRBSafeBody implements the KRB_SAFE_BODY of KRB_SAFE.
+type KRBSafeBody struct {
+	UserData       []byte            `asn1:"explicit,tag:0"`
+	Timestamp      time.Time         `asn1:"generalized,optional,explicit,tag:1"`
+	Usec           int               `asn1:"optional,explicit,tag:2"`
+	SequenceNumber int64             `asn1:"optional,explicit,tag:3"`
+	SAddress       types.HostAddress `asn1:"explicit,tag:4"`
+	RAddress       types.HostAddress `asn1:"optional,explicit,tag:5"`
+}
+
+// Unmarshal bytes b into the KRBSafe struct.
+func (s *KRBSafe) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, s, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.KRBSafe))
+	if err != nil {
+		return processUnmarshalReplyError(b, err)
+	}
+	expectedMsgType := msgtype.KRB_SAFE
+	if s.MsgType != expectedMsgType {
+		return krberror.NewErrorf(krberror.KRBMsgError, "message ID does not indicate a KRB_SAFE. Expected: %v; Actual: %v", expectedMsgType, s.MsgType)
+	}
+	return nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/messages/Ticket.go b/vendor/github.com/jcmturner/gokrb5/v8/messages/Ticket.go
new file mode 100644
index 0000000..11efad6
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/messages/Ticket.go
@@ -0,0 +1,262 @@
+package messages
+
+import (
+	"fmt"
+	"log"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/asn1tools"
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana"
+	"github.com/jcmturner/gokrb5/v8/iana/adtype"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+	"github.com/jcmturner/gokrb5/v8/iana/errorcode"
+	"github.com/jcmturner/gokrb5/v8/iana/flags"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/keytab"
+	"github.com/jcmturner/gokrb5/v8/krberror"
+	"github.com/jcmturner/gokrb5/v8/pac"
+	"github.com/jcmturner/gokrb5/v8/types"
+)
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.3
+
+// Ticket implements the Kerberos ticket.
+type Ticket struct {
+	TktVNO           int                 `asn1:"explicit,tag:0"`
+	Realm            string              `asn1:"generalstring,explicit,tag:1"`
+	SName            types.PrincipalName `asn1:"explicit,tag:2"`
+	EncPart          types.EncryptedData `asn1:"explicit,tag:3"`
+	DecryptedEncPart EncTicketPart       `asn1:"optional"` // Not part of ASN1 bytes so marked as optional so unmarshalling works
+}
+
+// EncTicketPart is the encrypted part of the Ticket.
+type EncTicketPart struct {
+	Flags             asn1.BitString          `asn1:"explicit,tag:0"`
+	Key               types.EncryptionKey     `asn1:"explicit,tag:1"`
+	CRealm            string                  `asn1:"generalstring,explicit,tag:2"`
+	CName             types.PrincipalName     `asn1:"explicit,tag:3"`
+	Transited         TransitedEncoding       `asn1:"explicit,tag:4"`
+	AuthTime          time.Time               `asn1:"generalized,explicit,tag:5"`
+	StartTime         time.Time               `asn1:"generalized,explicit,optional,tag:6"`
+	EndTime           time.Time               `asn1:"generalized,explicit,tag:7"`
+	RenewTill         time.Time               `asn1:"generalized,explicit,optional,tag:8"`
+	CAddr             types.HostAddresses     `asn1:"explicit,optional,tag:9"`
+	AuthorizationData types.AuthorizationData `asn1:"explicit,optional,tag:10"`
+}
+
+// TransitedEncoding part of the ticket's encrypted part.
+type TransitedEncoding struct {
+	TRType   int32  `asn1:"explicit,tag:0"`
+	Contents []byte `asn1:"explicit,tag:1"`
+}
+
+// NewTicket creates a new Ticket instance.
+func NewTicket(cname types.PrincipalName, crealm string, sname types.PrincipalName, srealm string, flags asn1.BitString, sktab *keytab.Keytab, eTypeID int32, kvno int, authTime, startTime, endTime, renewTill time.Time) (Ticket, types.EncryptionKey, error) {
+	etype, err := crypto.GetEtype(eTypeID)
+	if err != nil {
+		return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting etype for new ticket")
+	}
+	sessionKey, err := types.GenerateEncryptionKey(etype)
+	if err != nil {
+		return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error generating session key")
+	}
+
+	etp := EncTicketPart{
+		Flags:     flags,
+		Key:       sessionKey,
+		CRealm:    crealm,
+		CName:     cname,
+		Transited: TransitedEncoding{},
+		AuthTime:  authTime,
+		StartTime: startTime,
+		EndTime:   endTime,
+		RenewTill: renewTill,
+	}
+	b, err := asn1.Marshal(etp)
+	if err != nil {
+		return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncodingError, "error marshalling ticket encpart")
+	}
+	b = asn1tools.AddASNAppTag(b, asnAppTag.EncTicketPart)
+	skey, _, err := sktab.GetEncryptionKey(sname, srealm, kvno, eTypeID)
+	if err != nil {
+		return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error getting encryption key for new ticket")
+	}
+	ed, err := crypto.GetEncryptedData(b, skey, keyusage.KDC_REP_TICKET, kvno)
+	if err != nil {
+		return Ticket{}, types.EncryptionKey{}, krberror.Errorf(err, krberror.EncryptingError, "error encrypting ticket encpart")
+	}
+	tkt := Ticket{
+		TktVNO:  iana.PVNO,
+		Realm:   srealm,
+		SName:   sname,
+		EncPart: ed,
+	}
+	return tkt, sessionKey, nil
+}
+
+// Unmarshal bytes b into a Ticket struct.
+func (t *Ticket) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, t, fmt.Sprintf("application,explicit,tag:%d", asnAppTag.Ticket))
+	return err
+}
+
+// Marshal the Ticket.
+func (t *Ticket) Marshal() ([]byte, error) {
+	b, err := asn1.Marshal(*t)
+	if err != nil {
+		return nil, err
+	}
+	b = asn1tools.AddASNAppTag(b, asnAppTag.Ticket)
+	return b, nil
+}
+
+// Unmarshal bytes b into the EncTicketPart struct.
+func (t *EncTicketPart) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, t, fmt.Sprintf("application,explicit,tag:%d", asnAppTag.EncTicketPart))
+	return err
+}
+
+// unmarshalTicket returns a ticket from the bytes provided.
+func unmarshalTicket(b []byte) (t Ticket, err error) {
+	err = t.Unmarshal(b)
+	return
+}
+
+// UnmarshalTicketsSequence returns a slice of Tickets from a raw ASN1 value.
+func unmarshalTicketsSequence(in asn1.RawValue) ([]Ticket, error) {
+	//This is a workaround to a asn1 decoding issue in golang - https://github.com/golang/go/issues/17321. It's not pretty I'm afraid
+	//We pull out raw values from the larger raw value (that is actually the data of the sequence of raw values) and track our position moving along the data.
+	b := in.Bytes
+	// Ignore the head of the asn1 stream (1 byte for tag and those for the length) as this is what tells us its a sequence but we're handling it ourselves
+	p := 1 + asn1tools.GetNumberBytesInLengthHeader(in.Bytes)
+	var tkts []Ticket
+	var raw asn1.RawValue
+	for p < (len(b)) {
+		_, err := asn1.UnmarshalWithParams(b[p:], &raw, fmt.Sprintf("application,tag:%d", asnAppTag.Ticket))
+		if err != nil {
+			return nil, fmt.Errorf("unmarshaling sequence of tickets failed getting length of ticket: %v", err)
+		}
+		t, err := unmarshalTicket(b[p:])
+		if err != nil {
+			return nil, fmt.Errorf("unmarshaling sequence of tickets failed: %v", err)
+		}
+		p += len(raw.FullBytes)
+		tkts = append(tkts, t)
+	}
+	MarshalTicketSequence(tkts)
+	return tkts, nil
+}
+
+// MarshalTicketSequence marshals a slice of Tickets returning an ASN1 raw value containing the ticket sequence.
+func MarshalTicketSequence(tkts []Ticket) (asn1.RawValue, error) {
+	raw := asn1.RawValue{
+		Class:      2,
+		IsCompound: true,
+	}
+	if len(tkts) < 1 {
+		// There are no tickets to marshal
+		return raw, nil
+	}
+	var btkts []byte
+	for i, t := range tkts {
+		b, err := t.Marshal()
+		if err != nil {
+			return raw, fmt.Errorf("error marshaling ticket number %d in sequence of tickets", i+1)
+		}
+		btkts = append(btkts, b...)
+	}
+	// The ASN1 wrapping consists of 2 bytes:
+	// 1st byte -> Identifier Octet - In this case an OCTET STRING (ASN TAG
+	// 2nd byte -> The length (this will be the size indicated in the input bytes + 2 for the additional bytes we add here.
+	// Application Tag:
+	//| Byte:       | 8                            | 7                          | 6                                         | 5 | 4 | 3 | 2 | 1             |
+	//| Value:      | 0                            | 1                          | 1                                         | From the RFC spec 4120        |
+	//| Explanation | Defined by the ASN1 encoding rules for an application tag | A value of 1 indicates a constructed type | The ASN Application tag value |
+	btkts = append(asn1tools.MarshalLengthBytes(len(btkts)), btkts...)
+	btkts = append([]byte{byte(32 + asn1.TagSequence)}, btkts...)
+	raw.Bytes = btkts
+	// If we need to create the full bytes then identifier octet is "context-specific" = 128 + "constructed" + 32 + the wrapping explicit tag (11)
+	//fmt.Fprintf(os.Stderr, "mRaw fb: %v\n", raw.FullBytes)
+	return raw, nil
+}
+
+// DecryptEncPart decrypts the encrypted part of the ticket.
+// The sname argument can be used to specify which service principal's key should be used to decrypt the ticket.
+// If nil is passed as the sname then the service principal specified within the ticket it used.
+func (t *Ticket) DecryptEncPart(keytab *keytab.Keytab, sname *types.PrincipalName) error {
+	if sname == nil {
+		sname = &t.SName
+	}
+	key, _, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType)
+	if err != nil {
+		return NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err))
+	}
+	return t.Decrypt(key)
+}
+
+// Decrypt decrypts the encrypted part of the ticket using the key provided.
+func (t *Ticket) Decrypt(key types.EncryptionKey) error {
+	b, err := crypto.DecryptEncPart(t.EncPart, key, keyusage.KDC_REP_TICKET)
+	if err != nil {
+		return fmt.Errorf("error decrypting Ticket EncPart: %v", err)
+	}
+	var denc EncTicketPart
+	err = denc.Unmarshal(b)
+	if err != nil {
+		return fmt.Errorf("error unmarshaling encrypted part: %v", err)
+	}
+	t.DecryptedEncPart = denc
+	return nil
+}
+
+// GetPACType returns a Microsoft PAC that has been extracted from the ticket and processed.
+func (t *Ticket) GetPACType(keytab *keytab.Keytab, sname *types.PrincipalName, l *log.Logger) (bool, pac.PACType, error) {
+	var isPAC bool
+	for _, ad := range t.DecryptedEncPart.AuthorizationData {
+		if ad.ADType == adtype.ADIfRelevant {
+			var ad2 types.AuthorizationData
+			err := ad2.Unmarshal(ad.ADData)
+			if err != nil {
+				l.Printf("PAC authorization data could not be unmarshaled: %v", err)
+				continue
+			}
+			if ad2[0].ADType == adtype.ADWin2KPAC {
+				isPAC = true
+				var p pac.PACType
+				err = p.Unmarshal(ad2[0].ADData)
+				if err != nil {
+					return isPAC, p, fmt.Errorf("error unmarshaling PAC: %v", err)
+				}
+				if sname == nil {
+					sname = &t.SName
+				}
+				key, _, err := keytab.GetEncryptionKey(*sname, t.Realm, t.EncPart.KVNO, t.EncPart.EType)
+				if err != nil {
+					return isPAC, p, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_NOKEY, fmt.Sprintf("Could not get key from keytab: %v", err))
+				}
+				err = p.ProcessPACInfoBuffers(key, l)
+				return isPAC, p, err
+			}
+		}
+	}
+	return isPAC, pac.PACType{}, nil
+}
+
+// Valid checks it the ticket is currently valid. Max duration passed endtime passed in as argument.
+func (t *Ticket) Valid(d time.Duration) (bool, error) {
+	// Check for future tickets or invalid tickets
+	time := time.Now().UTC()
+	if t.DecryptedEncPart.StartTime.Sub(time) > d || types.IsFlagSet(&t.DecryptedEncPart.Flags, flags.Invalid) {
+		return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_NYV, "service ticket provided is not yet valid")
+	}
+
+	// Check for expired ticket
+	if time.Sub(t.DecryptedEncPart.EndTime) > d {
+		return false, NewKRBError(t.SName, t.Realm, errorcode.KRB_AP_ERR_TKT_EXPIRED, "service ticket provided has expired")
+	}
+
+	return true, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/client_claims.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/client_claims.go
new file mode 100644
index 0000000..36871e0
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/client_claims.go
@@ -0,0 +1,34 @@
+package pac
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/jcmturner/rpc/v2/mstypes"
+	"github.com/jcmturner/rpc/v2/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 {
+	ClaimsSetMetadata mstypes.ClaimsSetMetadata
+	ClaimsSet         mstypes.ClaimsSet
+}
+
+// Unmarshal bytes into the ClientClaimsInfo struct
+func (k *ClientClaimsInfo) Unmarshal(b []byte) (err error) {
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	m := new(mstypes.ClaimsSetMetadata)
+	err = dec.Decode(m)
+	if err != nil {
+		err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSetMetadata: %v", err)
+		return
+	}
+	k.ClaimsSetMetadata = *m
+	k.ClaimsSet, err = k.ClaimsSetMetadata.ClaimsSet()
+	if err != nil {
+		err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSet: %v", err)
+	}
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/client_info.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/client_info.go
new file mode 100644
index 0000000..ddd9578
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/client_info.go
@@ -0,0 +1,31 @@
+package pac
+
+import (
+	"bytes"
+
+	"github.com/jcmturner/rpc/v2/mstypes"
+)
+
+// ClientInfo implements https://msdn.microsoft.com/en-us/library/cc237951.aspx
+type ClientInfo struct {
+	ClientID   mstypes.FileTime // A FILETIME structure in little-endian format that contains the Kerberos initial ticket-granting ticket TGT authentication time
+	NameLength uint16           // An unsigned 16-bit integer in little-endian format that specifies the length, in bytes, of the Name field.
+	Name       string           // An array of 16-bit Unicode characters in little-endian format that contains the client's account name.
+}
+
+// Unmarshal bytes into the ClientInfo struct
+func (k *ClientInfo) Unmarshal(b []byte) (err error) {
+	//The PAC_CLIENT_INFO structure is a simple structure that is not NDR-encoded.
+	r := mstypes.NewReader(bytes.NewReader(b))
+
+	k.ClientID, err = r.FileTime()
+	if err != nil {
+		return
+	}
+	k.NameLength, err = r.Uint16()
+	if err != nil {
+		return
+	}
+	k.Name, err = r.UTF16String(int(k.NameLength))
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/credentials_info.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/credentials_info.go
new file mode 100644
index 0000000..0c7ccd4
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/credentials_info.go
@@ -0,0 +1,86 @@
+package pac
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/types"
+	"github.com/jcmturner/rpc/v2/mstypes"
+	"github.com/jcmturner/rpc/v2/ndr"
+)
+
+// https://msdn.microsoft.com/en-us/library/cc237931.aspx
+
+// CredentialsInfo implements https://msdn.microsoft.com/en-us/library/cc237953.aspx
+type CredentialsInfo struct {
+	Version                    uint32 // A 32-bit unsigned integer in little-endian format that defines the version. MUST be 0x00000000.
+	EType                      uint32
+	PACCredentialDataEncrypted []byte // Key usage number for encryption: KERB_NON_KERB_SALT (16)
+	PACCredentialData          CredentialData
+}
+
+// Unmarshal bytes into the CredentialsInfo struct
+func (c *CredentialsInfo) Unmarshal(b []byte, k types.EncryptionKey) (err error) {
+	//The CredentialsInfo structure is a simple structure that is not NDR-encoded.
+	r := mstypes.NewReader(bytes.NewReader(b))
+
+	c.Version, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	if c.Version != 0 {
+		err = errors.New("credentials info version is not zero")
+		return
+	}
+	c.EType, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	c.PACCredentialDataEncrypted, err = r.ReadBytes(len(b) - 8)
+	if err != nil {
+		err = fmt.Errorf("error reading PAC Credetials Data: %v", err)
+		return
+	}
+
+	err = c.DecryptEncPart(k)
+	if err != nil {
+		err = fmt.Errorf("error decrypting PAC Credentials Data: %v", err)
+		return
+	}
+	return
+}
+
+// DecryptEncPart decrypts the encrypted part of the CredentialsInfo.
+func (c *CredentialsInfo) DecryptEncPart(k types.EncryptionKey) error {
+	if k.KeyType != int32(c.EType) {
+		return fmt.Errorf("key provided is not the correct type. Type needed: %d, type provided: %d", c.EType, k.KeyType)
+	}
+	pt, err := crypto.DecryptMessage(c.PACCredentialDataEncrypted, k, keyusage.KERB_NON_KERB_SALT)
+	if err != nil {
+		return err
+	}
+	err = c.PACCredentialData.Unmarshal(pt)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// CredentialData implements https://msdn.microsoft.com/en-us/library/cc237952.aspx
+type CredentialData struct {
+	CredentialCount uint32
+	Credentials     []SECPKGSupplementalCred // Size is the value of CredentialCount
+}
+
+// Unmarshal converts the bytes provided into a CredentialData type.
+func (c *CredentialData) Unmarshal(b []byte) (err error) {
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err = dec.Decode(c)
+	if err != nil {
+		err = fmt.Errorf("error unmarshaling KerbValidationInfo: %v", err)
+	}
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/device_claims.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/device_claims.go
new file mode 100644
index 0000000..6eb2926
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/device_claims.go
@@ -0,0 +1,34 @@
+package pac
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/jcmturner/rpc/v2/mstypes"
+	"github.com/jcmturner/rpc/v2/ndr"
+)
+
+// Claims reference: https://msdn.microsoft.com/en-us/library/hh553895.aspx
+
+// DeviceClaimsInfo implements https://msdn.microsoft.com/en-us/library/hh554226.aspx
+type DeviceClaimsInfo struct {
+	ClaimsSetMetadata mstypes.ClaimsSetMetadata
+	ClaimsSet         mstypes.ClaimsSet
+}
+
+// Unmarshal bytes into the ClientClaimsInfo struct
+func (k *DeviceClaimsInfo) Unmarshal(b []byte) (err error) {
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	m := new(mstypes.ClaimsSetMetadata)
+	err = dec.Decode(m)
+	if err != nil {
+		err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSetMetadata: %v", err)
+		return
+	}
+	k.ClaimsSetMetadata = *m
+	k.ClaimsSet, err = k.ClaimsSetMetadata.ClaimsSet()
+	if err != nil {
+		err = fmt.Errorf("error unmarshaling ClientClaimsInfo ClaimsSet: %v", err)
+	}
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/device_info.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/device_info.go
new file mode 100644
index 0000000..ce82daa
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/device_info.go
@@ -0,0 +1,32 @@
+package pac
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/jcmturner/rpc/v2/mstypes"
+	"github.com/jcmturner/rpc/v2/ndr"
+)
+
+// DeviceInfo implements https://msdn.microsoft.com/en-us/library/hh536402.aspx
+type DeviceInfo struct {
+	UserID            uint32                          // A 32-bit unsigned integer that contains the RID of the account. If the UserId member equals 0x00000000, the first group SID in this member is the SID for this account.
+	PrimaryGroupID    uint32                          // A 32-bit unsigned integer that contains the RID for the primary group to which this account belongs.
+	AccountDomainID   mstypes.RPCSID                  `ndr:"pointer"` // A SID structure that contains the SID for the domain of the account.This member is used in conjunction with the UserId, and GroupIds members to create the user and group SIDs for the client.
+	AccountGroupCount uint32                          // A 32-bit unsigned integer that contains the number of groups within the account domain to which the account belongs
+	AccountGroupIDs   []mstypes.GroupMembership       `ndr:"pointer,conformant"` // A pointer to a list of GROUP_MEMBERSHIP (section 2.2.2) structures that contains the groups to which the account belongs in the account domain. The number of groups in this list MUST be equal to GroupCount.
+	SIDCount          uint32                          // A 32-bit unsigned integer that contains the total number of SIDs present in the ExtraSids member.
+	ExtraSIDs         []mstypes.KerbSidAndAttributes  `ndr:"pointer,conformant"` // A pointer to a list of KERB_SID_AND_ATTRIBUTES structures that contain a list of SIDs corresponding to groups not in domains. If the UserId member equals 0x00000000, the first group SID in this member is the SID for this account.
+	DomainGroupCount  uint32                          // A 32-bit unsigned integer that contains the number of domains with groups to which the account belongs.
+	DomainGroup       []mstypes.DomainGroupMembership `ndr:"pointer,conformant"` // A pointer to a list of DOMAIN_GROUP_MEMBERSHIP structures (section 2.2.3) that contains the domains to which the account belongs to a group. The number of sets in this list MUST be equal to DomainCount.
+}
+
+// Unmarshal bytes into the DeviceInfo struct
+func (k *DeviceInfo) Unmarshal(b []byte) (err error) {
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err = dec.Decode(k)
+	if err != nil {
+		err = fmt.Errorf("error unmarshaling DeviceInfo: %v", err)
+	}
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/kerb_validation_info.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/kerb_validation_info.go
new file mode 100644
index 0000000..dde7861
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/kerb_validation_info.go
@@ -0,0 +1,110 @@
+// Package pac implements Microsoft Privilege Attribute Certificate (PAC) processing.
+package pac
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/jcmturner/rpc/v2/mstypes"
+	"github.com/jcmturner/rpc/v2/ndr"
+)
+
+// KERB_VALIDATION_INFO flags.
+const (
+	USERFLAG_GUEST                                    = 31 // Authentication was done via the GUEST account; no password was used.
+	USERFLAG_NO_ENCRYPTION_AVAILABLE                  = 30 // No encryption is available.
+	USERFLAG_LAN_MANAGER_KEY                          = 28 // LAN Manager key was used for authentication.
+	USERFLAG_SUB_AUTH                                 = 25 // Sub-authentication used; session key came from the sub-authentication package.
+	USERFLAG_EXTRA_SIDS                               = 26 // Indicates that the ExtraSids field is populated and contains additional SIDs.
+	USERFLAG_MACHINE_ACCOUNT                          = 24 // Indicates that the account is a machine account.
+	USERFLAG_DC_NTLM2                                 = 23 // Indicates that the domain controller understands NTLMv2.
+	USERFLAG_RESOURCE_GROUPIDS                        = 22 // Indicates that the ResourceGroupIds field is populated.
+	USERFLAG_PROFILEPATH                              = 21 // Indicates that ProfilePath is populated.
+	USERFLAG_NTLM2_NTCHALLENGERESP                    = 20 // The NTLMv2 response from the NtChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used for authentication and session key generation.
+	USERFLAG_LM2_LMCHALLENGERESP                      = 19 // The LMv2 response from the LmChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used for authentication and session key generation.
+	USERFLAG_AUTH_LMCHALLENGERESP_KEY_NTCHALLENGERESP = 18 // The LMv2 response from the LmChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used for authentication and the NTLMv2 response from the NtChallengeResponseFields ([MS-NLMP] section 2.2.1.3) was used session key generation.
+)
+
+// KerbValidationInfo implement https://msdn.microsoft.com/en-us/library/cc237948.aspx
+type KerbValidationInfo struct {
+	LogOnTime              mstypes.FileTime
+	LogOffTime             mstypes.FileTime
+	KickOffTime            mstypes.FileTime
+	PasswordLastSet        mstypes.FileTime
+	PasswordCanChange      mstypes.FileTime
+	PasswordMustChange     mstypes.FileTime
+	EffectiveName          mstypes.RPCUnicodeString
+	FullName               mstypes.RPCUnicodeString
+	LogonScript            mstypes.RPCUnicodeString
+	ProfilePath            mstypes.RPCUnicodeString
+	HomeDirectory          mstypes.RPCUnicodeString
+	HomeDirectoryDrive     mstypes.RPCUnicodeString
+	LogonCount             uint16
+	BadPasswordCount       uint16
+	UserID                 uint32
+	PrimaryGroupID         uint32
+	GroupCount             uint32
+	GroupIDs               []mstypes.GroupMembership `ndr:"pointer,conformant"`
+	UserFlags              uint32
+	UserSessionKey         mstypes.UserSessionKey
+	LogonServer            mstypes.RPCUnicodeString
+	LogonDomainName        mstypes.RPCUnicodeString
+	LogonDomainID          mstypes.RPCSID `ndr:"pointer"`
+	Reserved1              [2]uint32      // Has 2 elements
+	UserAccountControl     uint32
+	SubAuthStatus          uint32
+	LastSuccessfulILogon   mstypes.FileTime
+	LastFailedILogon       mstypes.FileTime
+	FailedILogonCount      uint32
+	Reserved3              uint32
+	SIDCount               uint32
+	ExtraSIDs              []mstypes.KerbSidAndAttributes `ndr:"pointer,conformant"`
+	ResourceGroupDomainSID mstypes.RPCSID                 `ndr:"pointer"`
+	ResourceGroupCount     uint32
+	ResourceGroupIDs       []mstypes.GroupMembership `ndr:"pointer,conformant"`
+}
+
+// Unmarshal bytes into the DeviceInfo struct
+func (k *KerbValidationInfo) Unmarshal(b []byte) (err error) {
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err = dec.Decode(k)
+	if err != nil {
+		err = fmt.Errorf("error unmarshaling KerbValidationInfo: %v", err)
+	}
+	return
+}
+
+// GetGroupMembershipSIDs returns a slice of strings containing the group membership SIDs found in the PAC.
+func (k *KerbValidationInfo) GetGroupMembershipSIDs() []string {
+	var g []string
+	lSID := k.LogonDomainID.String()
+	for i := range k.GroupIDs {
+		g = append(g, fmt.Sprintf("%s-%d", lSID, k.GroupIDs[i].RelativeID))
+	}
+	for _, s := range k.ExtraSIDs {
+		var exists = false
+		for _, es := range g {
+			if es == s.SID.String() {
+				exists = true
+				break
+			}
+		}
+		if !exists {
+			g = append(g, s.SID.String())
+		}
+	}
+	for _, r := range k.ResourceGroupIDs {
+		var exists = false
+		s := fmt.Sprintf("%s-%d", k.ResourceGroupDomainSID.String(), r.RelativeID)
+		for _, es := range g {
+			if es == s {
+				exists = true
+				break
+			}
+		}
+		if !exists {
+			g = append(g, s)
+		}
+	}
+	return g
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/pac_type.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/pac_type.go
new file mode 100644
index 0000000..fab2ad7
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/pac_type.go
@@ -0,0 +1,251 @@
+package pac
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"log"
+
+	"github.com/jcmturner/gokrb5/v8/crypto"
+	"github.com/jcmturner/gokrb5/v8/iana/keyusage"
+	"github.com/jcmturner/gokrb5/v8/types"
+	"github.com/jcmturner/rpc/v2/mstypes"
+)
+
+const (
+	infoTypeKerbValidationInfo     uint32 = 1
+	infoTypeCredentials            uint32 = 2
+	infoTypePACServerSignatureData uint32 = 6
+	infoTypePACKDCSignatureData    uint32 = 7
+	infoTypePACClientInfo          uint32 = 10
+	infoTypeS4UDelegationInfo      uint32 = 11
+	infoTypeUPNDNSInfo             uint32 = 12
+	infoTypePACClientClaimsInfo    uint32 = 13
+	infoTypePACDeviceInfo          uint32 = 14
+	infoTypePACDeviceClaimsInfo    uint32 = 15
+)
+
+// PACType implements: https://msdn.microsoft.com/en-us/library/cc237950.aspx
+type PACType struct {
+	CBuffers           uint32
+	Version            uint32
+	Buffers            []InfoBuffer
+	Data               []byte
+	KerbValidationInfo *KerbValidationInfo
+	CredentialsInfo    *CredentialsInfo
+	ServerChecksum     *SignatureData
+	KDCChecksum        *SignatureData
+	ClientInfo         *ClientInfo
+	S4UDelegationInfo  *S4UDelegationInfo
+	UPNDNSInfo         *UPNDNSInfo
+	ClientClaimsInfo   *ClientClaimsInfo
+	DeviceInfo         *DeviceInfo
+	DeviceClaimsInfo   *DeviceClaimsInfo
+	ZeroSigData        []byte
+}
+
+// InfoBuffer implements the PAC Info Buffer: https://msdn.microsoft.com/en-us/library/cc237954.aspx
+type InfoBuffer struct {
+	ULType       uint32 // A 32-bit unsigned integer in little-endian format that describes the type of data present in the buffer contained at Offset.
+	CBBufferSize uint32 // A 32-bit unsigned integer in little-endian format that contains the size, in bytes, of the buffer in the PAC located at Offset.
+	Offset       uint64 // A 64-bit unsigned integer in little-endian format that contains the offset to the beginning of the buffer, in bytes, from the beginning of the PACTYPE structure. The data offset MUST be a multiple of eight. The following sections specify the format of each type of element.
+}
+
+// Unmarshal bytes into the PACType struct
+func (pac *PACType) Unmarshal(b []byte) (err error) {
+	pac.Data = b
+	zb := make([]byte, len(b), len(b))
+	copy(zb, b)
+	pac.ZeroSigData = zb
+	r := mstypes.NewReader(bytes.NewReader(b))
+	pac.CBuffers, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	pac.Version, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	buf := make([]InfoBuffer, pac.CBuffers, pac.CBuffers)
+	for i := range buf {
+		buf[i].ULType, err = r.Uint32()
+		if err != nil {
+			return
+		}
+		buf[i].CBBufferSize, err = r.Uint32()
+		if err != nil {
+			return
+		}
+		buf[i].Offset, err = r.Uint64()
+		if err != nil {
+			return
+		}
+	}
+	pac.Buffers = buf
+	return nil
+}
+
+// ProcessPACInfoBuffers processes the PAC Info Buffers.
+// https://msdn.microsoft.com/en-us/library/cc237954.aspx
+func (pac *PACType) ProcessPACInfoBuffers(key types.EncryptionKey, l *log.Logger) error {
+	for _, buf := range pac.Buffers {
+		p := make([]byte, buf.CBBufferSize, buf.CBBufferSize)
+		copy(p, pac.Data[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)])
+		switch buf.ULType {
+		case infoTypeKerbValidationInfo:
+			if pac.KerbValidationInfo != nil {
+				//Must ignore subsequent buffers of this type
+				continue
+			}
+			var k KerbValidationInfo
+			err := k.Unmarshal(p)
+			if err != nil {
+				return fmt.Errorf("error processing KerbValidationInfo: %v", err)
+			}
+			pac.KerbValidationInfo = &k
+		case infoTypeCredentials:
+			// 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 infoTypePACServerSignatureData:
+			if pac.ServerChecksum != nil {
+				//Must ignore subsequent buffers of this type
+				continue
+			}
+			var k SignatureData
+			zb, err := k.Unmarshal(p)
+			copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb)
+			if err != nil {
+				return fmt.Errorf("error processing ServerChecksum: %v", err)
+			}
+			pac.ServerChecksum = &k
+		case infoTypePACKDCSignatureData:
+			if pac.KDCChecksum != nil {
+				//Must ignore subsequent buffers of this type
+				continue
+			}
+			var k SignatureData
+			zb, err := k.Unmarshal(p)
+			copy(pac.ZeroSigData[int(buf.Offset):int(buf.Offset)+int(buf.CBBufferSize)], zb)
+			if err != nil {
+				return fmt.Errorf("error processing KDCChecksum: %v", err)
+			}
+			pac.KDCChecksum = &k
+		case infoTypePACClientInfo:
+			if pac.ClientInfo != nil {
+				//Must ignore subsequent buffers of this type
+				continue
+			}
+			var k ClientInfo
+			err := k.Unmarshal(p)
+			if err != nil {
+				return fmt.Errorf("error processing ClientInfo: %v", err)
+			}
+			pac.ClientInfo = &k
+		case infoTypeS4UDelegationInfo:
+			if pac.S4UDelegationInfo != nil {
+				//Must ignore subsequent buffers of this type
+				continue
+			}
+			var k S4UDelegationInfo
+			err := k.Unmarshal(p)
+			if err != nil {
+				l.Printf("could not process S4U_DelegationInfo: %v", err)
+				continue
+			}
+			pac.S4UDelegationInfo = &k
+		case infoTypeUPNDNSInfo:
+			if pac.UPNDNSInfo != nil {
+				//Must ignore subsequent buffers of this type
+				continue
+			}
+			var k UPNDNSInfo
+			err := k.Unmarshal(p)
+			if err != nil {
+				l.Printf("could not process UPN_DNSInfo: %v", err)
+				continue
+			}
+			pac.UPNDNSInfo = &k
+		case infoTypePACClientClaimsInfo:
+			if pac.ClientClaimsInfo != nil || len(p) < 1 {
+				//Must ignore subsequent buffers of this type
+				continue
+			}
+			var k ClientClaimsInfo
+			err := k.Unmarshal(p)
+			if err != nil {
+				l.Printf("could not process ClientClaimsInfo: %v", err)
+				continue
+			}
+			pac.ClientClaimsInfo = &k
+		case infoTypePACDeviceInfo:
+			if pac.DeviceInfo != nil {
+				//Must ignore subsequent buffers of this type
+				continue
+			}
+			var k DeviceInfo
+			err := k.Unmarshal(p)
+			if err != nil {
+				l.Printf("could not process DeviceInfo: %v", err)
+				continue
+			}
+			pac.DeviceInfo = &k
+		case infoTypePACDeviceClaimsInfo:
+			if pac.DeviceClaimsInfo != nil {
+				//Must ignore subsequent buffers of this type
+				continue
+			}
+			var k DeviceClaimsInfo
+			err := k.Unmarshal(p)
+			if err != nil {
+				l.Printf("could not process DeviceClaimsInfo: %v", err)
+				continue
+			}
+			pac.DeviceClaimsInfo = &k
+		}
+	}
+
+	if ok, err := pac.verify(key); !ok {
+		return err
+	}
+
+	return nil
+}
+
+func (pac *PACType) verify(key types.EncryptionKey) (bool, error) {
+	if pac.KerbValidationInfo == nil {
+		return false, errors.New("PAC Info Buffers does not contain a KerbValidationInfo")
+	}
+	if pac.ServerChecksum == nil {
+		return false, errors.New("PAC Info Buffers does not contain a ServerChecksum")
+	}
+	if pac.KDCChecksum == nil {
+		return false, errors.New("PAC Info Buffers does not contain a KDCChecksum")
+	}
+	if pac.ClientInfo == nil {
+		return false, errors.New("PAC Info Buffers does not contain a ClientInfo")
+	}
+	etype, err := crypto.GetChksumEtype(int32(pac.ServerChecksum.SignatureType))
+	if err != nil {
+		return false, err
+	}
+	if ok := etype.VerifyChecksum(key.KeyValue,
+		pac.ZeroSigData,
+		pac.ServerChecksum.Signature,
+		keyusage.KERB_NON_KERB_CKSUM_SALT); !ok {
+		return false, errors.New("PAC service checksum verification failed")
+	}
+
+	return true, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/s4u_delegation_info.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/s4u_delegation_info.go
new file mode 100644
index 0000000..da837d4
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/s4u_delegation_info.go
@@ -0,0 +1,26 @@
+package pac
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/jcmturner/rpc/v2/mstypes"
+	"github.com/jcmturner/rpc/v2/ndr"
+)
+
+// S4UDelegationInfo implements https://msdn.microsoft.com/en-us/library/cc237944.aspx
+type S4UDelegationInfo struct {
+	S4U2proxyTarget      mstypes.RPCUnicodeString // The name of the principal to whom the application can forward the ticket.
+	TransitedListSize    uint32
+	S4UTransitedServices []mstypes.RPCUnicodeString `ndr:"pointer,conformant"` // List of all services that have been delegated through by this client and subsequent services or servers.. Size is value of TransitedListSize
+}
+
+// Unmarshal bytes into the S4UDelegationInfo struct
+func (k *S4UDelegationInfo) Unmarshal(b []byte) (err error) {
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err = dec.Decode(k)
+	if err != nil {
+		err = fmt.Errorf("error unmarshaling S4UDelegationInfo: %v", err)
+	}
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/signature_data.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/signature_data.go
new file mode 100644
index 0000000..8f6aa58
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/signature_data.go
@@ -0,0 +1,67 @@
+package pac
+
+import (
+	"bytes"
+
+	"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
+	"github.com/jcmturner/rpc/v2/mstypes"
+)
+
+/*
+https://msdn.microsoft.com/en-us/library/cc237955.aspx
+
+The Key Usage Value MUST be KERB_NON_KERB_CKSUM_SALT (17) [MS-KILE] (section 3.1.5.9).
+
+Server Signature (SignatureType = 0x00000006)
+https://msdn.microsoft.com/en-us/library/cc237957.aspx
+
+KDC Signature (SignatureType = 0x00000007)
+https://msdn.microsoft.com/en-us/library/dd357117.aspx
+*/
+
+// SignatureData implements https://msdn.microsoft.com/en-us/library/cc237955.aspx
+type SignatureData struct {
+	SignatureType  uint32 // A 32-bit unsigned integer value in little-endian format that defines the cryptographic system used to calculate the checksum. This MUST be one of the following checksum types: KERB_CHECKSUM_HMAC_MD5 (signature size = 16), HMAC_SHA1_96_AES128 (signature size = 12), HMAC_SHA1_96_AES256 (signature size = 12).
+	Signature      []byte // Size depends on the type. See comment above.
+	RODCIdentifier uint16 // A 16-bit unsigned integer value in little-endian format that contains the first 16 bits of the key version number ([MS-KILE] section 3.1.5.8) when the KDC is an RODC. When the KDC is not an RODC, this field does not exist.
+}
+
+// Unmarshal bytes into the SignatureData struct
+func (k *SignatureData) Unmarshal(b []byte) (rb []byte, err error) {
+	r := mstypes.NewReader(bytes.NewReader(b))
+
+	k.SignatureType, err = r.Uint32()
+	if err != nil {
+		return
+	}
+
+	var c int
+	switch k.SignatureType {
+	case chksumtype.KERB_CHECKSUM_HMAC_MD5_UNSIGNED:
+		c = 16
+	case uint32(chksumtype.HMAC_SHA1_96_AES128):
+		c = 12
+	case uint32(chksumtype.HMAC_SHA1_96_AES256):
+		c = 12
+	}
+	k.Signature, err = r.ReadBytes(c)
+	if err != nil {
+		return
+	}
+
+	// When the KDC is not an Read Only Domain Controller (RODC), this field does not exist.
+	if len(b) >= 4+c+2 {
+		k.RODCIdentifier, err = r.Uint16()
+		if err != nil {
+			return
+		}
+	}
+
+	// Create bytes with zeroed signature needed for checksum verification
+	rb = make([]byte, len(b), len(b))
+	copy(rb, b)
+	z := make([]byte, len(b), len(b))
+	copy(rb[4:4+c], z)
+
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/supplemental_cred.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/supplemental_cred.go
new file mode 100644
index 0000000..d40679d
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/supplemental_cred.go
@@ -0,0 +1,87 @@
+package pac
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+
+	"github.com/jcmturner/rpc/v2/mstypes"
+	"github.com/jcmturner/rpc/v2/ndr"
+)
+
+const (
+	// NTLMSupCredLMOWF indicates that the LM OWF member is present and valid.
+	NTLMSupCredLMOWF uint32 = 31
+	// NTLMSupCredNTOWF indicates that the NT OWF member is present and valid.
+	NTLMSupCredNTOWF uint32 = 30
+)
+
+// NTLMSupplementalCred implements https://msdn.microsoft.com/en-us/library/cc237949.aspx
+type NTLMSupplementalCred struct {
+	Version    uint32 // A 32-bit unsigned integer that defines the credential version.This field MUST be 0x00000000.
+	Flags      uint32
+	LMPassword []byte // A 16-element array of unsigned 8-bit integers that define the LM OWF. The LMPassword member MUST be ignored if the L flag is not set in the Flags member.
+	NTPassword []byte // A 16-element array of unsigned 8-bit integers that define the NT OWF. The NTPassword member MUST be ignored if the N flag is not set in the Flags member.
+}
+
+// Unmarshal converts the bytes provided into a NTLMSupplementalCred.
+func (c *NTLMSupplementalCred) Unmarshal(b []byte) (err error) {
+	r := mstypes.NewReader(bytes.NewReader(b))
+	c.Version, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	if c.Version != 0 {
+		err = errors.New("NTLMSupplementalCred version is not zero")
+		return
+	}
+	c.Flags, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	if isFlagSet(c.Flags, NTLMSupCredLMOWF) {
+		c.LMPassword, err = r.ReadBytes(16)
+		if err != nil {
+			return
+		}
+	}
+	if isFlagSet(c.Flags, NTLMSupCredNTOWF) {
+		c.NTPassword, err = r.ReadBytes(16)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+// isFlagSet tests if a flag is set in the uint32 little endian flag
+func isFlagSet(f uint32, i uint32) bool {
+	//Which byte?
+	b := int(i / 8)
+	//Which bit in byte
+	p := uint(7 - (int(i) - 8*b))
+	fb := make([]byte, 4)
+	binary.LittleEndian.PutUint32(fb, f)
+	if fb[b]&(1<<p) != 0 {
+		return true
+	}
+	return false
+}
+
+// SECPKGSupplementalCred implements https://msdn.microsoft.com/en-us/library/cc237956.aspx
+type SECPKGSupplementalCred struct {
+	PackageName    mstypes.RPCUnicodeString
+	CredentialSize uint32
+	Credentials    []uint8 `ndr:"pointer,conformant"` // Is a ptr. Size is the value of CredentialSize
+}
+
+// Unmarshal converts the bytes provided into a SECPKGSupplementalCred.
+func (c *SECPKGSupplementalCred) Unmarshal(b []byte) (err error) {
+	dec := ndr.NewDecoder(bytes.NewReader(b))
+	err = dec.Decode(c)
+	if err != nil {
+		err = fmt.Errorf("error unmarshaling SECPKGSupplementalCred: %v", err)
+	}
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/pac/upn_dns_info.go b/vendor/github.com/jcmturner/gokrb5/v8/pac/upn_dns_info.go
new file mode 100644
index 0000000..d374b96
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/pac/upn_dns_info.go
@@ -0,0 +1,73 @@
+package pac
+
+import (
+	"bytes"
+
+	"github.com/jcmturner/rpc/v2/mstypes"
+)
+
+// UPNDNSInfo implements https://msdn.microsoft.com/en-us/library/dd240468.aspx
+type UPNDNSInfo struct {
+	UPNLength           uint16 // An unsigned 16-bit integer in little-endian format that specifies the length, in bytes, of the UPN field.
+	UPNOffset           uint16 // An unsigned 16-bit integer in little-endian format that contains the offset to the beginning of the buffer, in bytes, from the beginning of the UPN_DNS_INFO structure.
+	DNSDomainNameLength uint16
+	DNSDomainNameOffset uint16
+	Flags               uint32
+	UPN                 string
+	DNSDomain           string
+}
+
+const (
+	upnNoUPNAttr = 31 // The user account object does not have the userPrincipalName attribute ([MS-ADA3] section 2.349) set. A UPN constructed by concatenating the user name with the DNS domain name of the account domain is provided.
+)
+
+// Unmarshal bytes into the UPN_DNSInfo struct
+func (k *UPNDNSInfo) Unmarshal(b []byte) (err error) {
+	//The UPN_DNS_INFO structure is a simple structure that is not NDR-encoded.
+	r := mstypes.NewReader(bytes.NewReader(b))
+	k.UPNLength, err = r.Uint16()
+	if err != nil {
+		return
+	}
+	k.UPNOffset, err = r.Uint16()
+	if err != nil {
+		return
+	}
+	k.DNSDomainNameLength, err = r.Uint16()
+	if err != nil {
+		return
+	}
+	k.DNSDomainNameOffset, err = r.Uint16()
+	if err != nil {
+		return
+	}
+	k.Flags, err = r.Uint32()
+	if err != nil {
+		return
+	}
+	ub := mstypes.NewReader(bytes.NewReader(b[k.UPNOffset : k.UPNOffset+k.UPNLength]))
+	db := mstypes.NewReader(bytes.NewReader(b[k.DNSDomainNameOffset : k.DNSDomainNameOffset+k.DNSDomainNameLength]))
+
+	u := make([]rune, k.UPNLength/2, k.UPNLength/2)
+	for i := 0; i < len(u); i++ {
+		var r uint16
+		r, err = ub.Uint16()
+		if err != nil {
+			return
+		}
+		u[i] = rune(r)
+	}
+	k.UPN = string(u)
+	d := make([]rune, k.DNSDomainNameLength/2, k.DNSDomainNameLength/2)
+	for i := 0; i < len(d); i++ {
+		var r uint16
+		r, err = db.Uint16()
+		if err != nil {
+			return
+		}
+		d[i] = rune(r)
+	}
+	k.DNSDomain = string(d)
+
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/types/Authenticator.go b/vendor/github.com/jcmturner/gokrb5/v8/types/Authenticator.go
new file mode 100644
index 0000000..1fdba78
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/types/Authenticator.go
@@ -0,0 +1,81 @@
+// Package types provides Kerberos 5 data types.
+package types
+
+import (
+	"crypto/rand"
+	"fmt"
+	"math"
+	"math/big"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/asn1tools"
+	"github.com/jcmturner/gokrb5/v8/iana"
+	"github.com/jcmturner/gokrb5/v8/iana/asnAppTag"
+)
+
+// Authenticator - A record containing information that can be shown to have been recently generated using the session
+// key known only by the client and server.
+// https://tools.ietf.org/html/rfc4120#section-5.5.1
+type Authenticator struct {
+	AVNO              int               `asn1:"explicit,tag:0"`
+	CRealm            string            `asn1:"generalstring,explicit,tag:1"`
+	CName             PrincipalName     `asn1:"explicit,tag:2"`
+	Cksum             Checksum          `asn1:"explicit,optional,tag:3"`
+	Cusec             int               `asn1:"explicit,tag:4"`
+	CTime             time.Time         `asn1:"generalized,explicit,tag:5"`
+	SubKey            EncryptionKey     `asn1:"explicit,optional,tag:6"`
+	SeqNumber         int64             `asn1:"explicit,optional,tag:7"`
+	AuthorizationData AuthorizationData `asn1:"explicit,optional,tag:8"`
+}
+
+// NewAuthenticator creates a new Authenticator.
+func NewAuthenticator(realm string, cname PrincipalName) (Authenticator, error) {
+	seq, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32))
+	if err != nil {
+		return Authenticator{}, err
+	}
+	t := time.Now().UTC()
+	return Authenticator{
+		AVNO:      iana.PVNO,
+		CRealm:    realm,
+		CName:     cname,
+		Cksum:     Checksum{},
+		Cusec:     int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)),
+		CTime:     t,
+		SeqNumber: seq.Int64(),
+	}, nil
+}
+
+// GenerateSeqNumberAndSubKey sets the Authenticator's sequence number and subkey.
+func (a *Authenticator) GenerateSeqNumberAndSubKey(keyType int32, keySize int) error {
+	seq, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32))
+	if err != nil {
+		return err
+	}
+	a.SeqNumber = seq.Int64()
+	//Generate subkey value
+	sk := make([]byte, keySize, keySize)
+	rand.Read(sk)
+	a.SubKey = EncryptionKey{
+		KeyType:  keyType,
+		KeyValue: sk,
+	}
+	return nil
+}
+
+// Unmarshal bytes into the Authenticator.
+func (a *Authenticator) Unmarshal(b []byte) error {
+	_, err := asn1.UnmarshalWithParams(b, a, fmt.Sprintf("application,explicit,tag:%v", asnAppTag.Authenticator))
+	return err
+}
+
+// Marshal the Authenticator.
+func (a *Authenticator) Marshal() ([]byte, error) {
+	b, err := asn1.Marshal(*a)
+	if err != nil {
+		return nil, err
+	}
+	b = asn1tools.AddASNAppTag(b, asnAppTag.Authenticator)
+	return b, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/types/AuthorizationData.go b/vendor/github.com/jcmturner/gokrb5/v8/types/AuthorizationData.go
new file mode 100644
index 0000000..80c477c
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/types/AuthorizationData.go
@@ -0,0 +1,55 @@
+package types
+
+import (
+	"github.com/jcmturner/gofork/encoding/asn1"
+)
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.6
+
+// AuthorizationData implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6
+type AuthorizationData []AuthorizationDataEntry
+
+// AuthorizationDataEntry implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6
+type AuthorizationDataEntry struct {
+	ADType int32  `asn1:"explicit,tag:0"`
+	ADData []byte `asn1:"explicit,tag:1"`
+}
+
+// ADIfRelevant implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.1
+type ADIfRelevant AuthorizationData
+
+// ADKDCIssued implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.2
+type ADKDCIssued struct {
+	ADChecksum Checksum          `asn1:"explicit,tag:0"`
+	IRealm     string            `asn1:"optional,generalstring,explicit,tag:1"`
+	Isname     PrincipalName     `asn1:"optional,explicit,tag:2"`
+	Elements   AuthorizationData `asn1:"explicit,tag:3"`
+}
+
+// ADAndOr implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.3
+type ADAndOr struct {
+	ConditionCount int32             `asn1:"explicit,tag:0"`
+	Elements       AuthorizationData `asn1:"explicit,tag:1"`
+}
+
+// ADMandatoryForKDC implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.6.4
+type ADMandatoryForKDC AuthorizationData
+
+// Unmarshal bytes into the ADKDCIssued.
+func (a *ADKDCIssued) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
+
+// Unmarshal bytes into the AuthorizationData.
+func (a *AuthorizationData) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
+
+// Unmarshal bytes into the AuthorizationDataEntry.
+func (a *AuthorizationDataEntry) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/types/Cryptosystem.go b/vendor/github.com/jcmturner/gokrb5/v8/types/Cryptosystem.go
new file mode 100644
index 0000000..2f354ea
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/types/Cryptosystem.go
@@ -0,0 +1,72 @@
+package types
+
+import (
+	"crypto/rand"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/crypto/etype"
+)
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.9
+
+// EncryptedData implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.9
+type EncryptedData struct {
+	EType  int32  `asn1:"explicit,tag:0"`
+	KVNO   int    `asn1:"explicit,optional,tag:1"`
+	Cipher []byte `asn1:"explicit,tag:2"`
+}
+
+// EncryptionKey implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.9
+// AKA KeyBlock
+type EncryptionKey struct {
+	KeyType  int32  `asn1:"explicit,tag:0"`
+	KeyValue []byte `asn1:"explicit,tag:1" json:"-"`
+}
+
+// Checksum implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.9
+type Checksum struct {
+	CksumType int32  `asn1:"explicit,tag:0"`
+	Checksum  []byte `asn1:"explicit,tag:1"`
+}
+
+// Unmarshal bytes into the EncryptedData.
+func (a *EncryptedData) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
+
+// Marshal the EncryptedData.
+func (a *EncryptedData) Marshal() ([]byte, error) {
+	edb, err := asn1.Marshal(*a)
+	if err != nil {
+		return edb, err
+	}
+	return edb, nil
+}
+
+// Unmarshal bytes into the EncryptionKey.
+func (a *EncryptionKey) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
+
+// Unmarshal bytes into the Checksum.
+func (a *Checksum) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
+
+// GenerateEncryptionKey creates a new EncryptionKey with a random key value.
+func GenerateEncryptionKey(etype etype.EType) (EncryptionKey, error) {
+	k := EncryptionKey{
+		KeyType: etype.GetETypeID(),
+	}
+	b := make([]byte, etype.GetKeyByteSize(), etype.GetKeyByteSize())
+	_, err := rand.Read(b)
+	if err != nil {
+		return k, err
+	}
+	k.KeyValue = b
+	return k, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/types/HostAddress.go b/vendor/github.com/jcmturner/gokrb5/v8/types/HostAddress.go
new file mode 100644
index 0000000..895fe80
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/types/HostAddress.go
@@ -0,0 +1,180 @@
+package types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.5
+
+import (
+	"bytes"
+	"fmt"
+	"net"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/iana/addrtype"
+)
+
+// HostAddresses implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.5
+type HostAddresses []HostAddress
+
+// HostAddress implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.5
+type HostAddress struct {
+	AddrType int32  `asn1:"explicit,tag:0"`
+	Address  []byte `asn1:"explicit,tag:1"`
+}
+
+// GetHostAddress returns a HostAddress struct from a string in the format <hostname>:<port>
+func GetHostAddress(s string) (HostAddress, error) {
+	var h HostAddress
+	cAddr, _, err := net.SplitHostPort(s)
+	if err != nil {
+		return h, fmt.Errorf("invalid format of client address: %v", err)
+	}
+	ip := net.ParseIP(cAddr)
+	var ht int32
+	if ip.To4() != nil {
+		ht = addrtype.IPv4
+		ip = ip.To4()
+	} else if ip.To16() != nil {
+		ht = addrtype.IPv6
+		ip = ip.To16()
+	} else {
+		return h, fmt.Errorf("could not determine client's address types: %v", err)
+	}
+	h = HostAddress{
+		AddrType: ht,
+		Address:  ip,
+	}
+	return h, nil
+}
+
+// GetAddress returns a string representation of the HostAddress.
+func (h *HostAddress) GetAddress() (string, error) {
+	var b []byte
+	_, err := asn1.Unmarshal(h.Address, &b)
+	return string(b), err
+}
+
+// LocalHostAddresses returns a HostAddresses struct for the local machines interface IP addresses.
+func LocalHostAddresses() (ha HostAddresses, err error) {
+	ifs, err := net.Interfaces()
+	if err != nil {
+		return
+	}
+	for _, iface := range ifs {
+		if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 {
+			// Interface is either loopback of not up
+			continue
+		}
+		addrs, err := iface.Addrs()
+		if err != nil {
+			continue
+		}
+		for _, addr := range addrs {
+			var ip net.IP
+			switch v := addr.(type) {
+			case *net.IPNet:
+				ip = v.IP
+			case *net.IPAddr:
+				ip = v.IP
+			}
+			var a HostAddress
+			if ip.To16() == nil {
+				//neither IPv4 or IPv6
+				continue
+			}
+			if ip.To4() != nil {
+				//Is IPv4
+				a.AddrType = addrtype.IPv4
+				a.Address = ip.To4()
+			} else {
+				a.AddrType = addrtype.IPv6
+				a.Address = ip.To16()
+			}
+			ha = append(ha, a)
+		}
+	}
+	return ha, nil
+}
+
+// HostAddressesFromNetIPs returns a HostAddresses type from a slice of net.IP
+func HostAddressesFromNetIPs(ips []net.IP) (ha HostAddresses) {
+	for _, ip := range ips {
+		ha = append(ha, HostAddressFromNetIP(ip))
+	}
+	return ha
+}
+
+// HostAddressFromNetIP returns a HostAddress type from a net.IP
+func HostAddressFromNetIP(ip net.IP) HostAddress {
+	if ip.To4() != nil {
+		//Is IPv4
+		return HostAddress{
+			AddrType: addrtype.IPv4,
+			Address:  ip.To4(),
+		}
+	}
+	return HostAddress{
+		AddrType: addrtype.IPv6,
+		Address:  ip.To16(),
+	}
+}
+
+// HostAddressesEqual tests if two HostAddress slices are equal.
+func HostAddressesEqual(h, a []HostAddress) bool {
+	if len(h) != len(a) {
+		return false
+	}
+	for _, e := range a {
+		var found bool
+		for _, i := range h {
+			if e.Equal(i) {
+				found = true
+				break
+			}
+		}
+		if !found {
+			return false
+		}
+	}
+	return true
+}
+
+// HostAddressesContains tests if a HostAddress is contained in a HostAddress slice.
+func HostAddressesContains(h []HostAddress, a HostAddress) bool {
+	for _, e := range h {
+		if e.Equal(a) {
+			return true
+		}
+	}
+	return false
+}
+
+// Equal tests if the HostAddress is equal to another HostAddress provided.
+func (h *HostAddress) Equal(a HostAddress) bool {
+	if h.AddrType != a.AddrType {
+		return false
+	}
+	return bytes.Equal(h.Address, a.Address)
+}
+
+// Contains tests if a HostAddress is contained within the HostAddresses struct.
+func (h *HostAddresses) Contains(a HostAddress) bool {
+	for _, e := range *h {
+		if e.Equal(a) {
+			return true
+		}
+	}
+	return false
+}
+
+// Equal tests if a HostAddress slice is equal to the HostAddresses struct.
+func (h *HostAddresses) Equal(a []HostAddress) bool {
+	if len(*h) != len(a) {
+		return false
+	}
+	for _, e := range a {
+		if !h.Contains(e) {
+			return false
+		}
+	}
+	return true
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/types/KerberosFlags.go b/vendor/github.com/jcmturner/gokrb5/v8/types/KerberosFlags.go
new file mode 100644
index 0000000..0f20383
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/types/KerberosFlags.go
@@ -0,0 +1,68 @@
+package types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.8
+
+import (
+	"github.com/jcmturner/gofork/encoding/asn1"
+)
+
+// NewKrbFlags returns an ASN1 BitString struct of the right size for KrbFlags.
+func NewKrbFlags() asn1.BitString {
+	f := asn1.BitString{}
+	f.Bytes = make([]byte, 4)
+	f.BitLength = len(f.Bytes) * 8
+	return f
+}
+
+// SetFlags sets the flags of an ASN1 BitString.
+func SetFlags(f *asn1.BitString, j []int) {
+	for _, i := range j {
+		SetFlag(f, i)
+	}
+}
+
+// SetFlag sets a flag in an ASN1 BitString.
+func SetFlag(f *asn1.BitString, i int) {
+	for l := len(f.Bytes); l < 4; l++ {
+		(*f).Bytes = append((*f).Bytes, byte(0))
+		(*f).BitLength = len((*f).Bytes) * 8
+	}
+	//Which byte?
+	b := i / 8
+	//Which bit in byte
+	p := uint(7 - (i - 8*b))
+	(*f).Bytes[b] = (*f).Bytes[b] | (1 << p)
+}
+
+// UnsetFlags unsets flags in an ASN1 BitString.
+func UnsetFlags(f *asn1.BitString, j []int) {
+	for _, i := range j {
+		UnsetFlag(f, i)
+	}
+}
+
+// UnsetFlag unsets a flag in an ASN1 BitString.
+func UnsetFlag(f *asn1.BitString, i int) {
+	for l := len(f.Bytes); l < 4; l++ {
+		(*f).Bytes = append((*f).Bytes, byte(0))
+		(*f).BitLength = len((*f).Bytes) * 8
+	}
+	//Which byte?
+	b := i / 8
+	//Which bit in byte
+	p := uint(7 - (i - 8*b))
+	(*f).Bytes[b] = (*f).Bytes[b] &^ (1 << p)
+}
+
+// IsFlagSet tests if a flag is set in the ASN1 BitString.
+func IsFlagSet(f *asn1.BitString, i int) bool {
+	//Which byte?
+	b := i / 8
+	//Which bit in byte
+	p := uint(7 - (i - 8*b))
+	if (*f).Bytes[b]&(1<<p) != 0 {
+		return true
+	}
+	return false
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/types/PAData.go b/vendor/github.com/jcmturner/gokrb5/v8/types/PAData.go
new file mode 100644
index 0000000..41645ec
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/types/PAData.go
@@ -0,0 +1,155 @@
+package types
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.7
+import (
+	"fmt"
+	"time"
+
+	"github.com/jcmturner/gofork/encoding/asn1"
+	"github.com/jcmturner/gokrb5/v8/iana/patype"
+)
+
+// PAData implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7
+type PAData struct {
+	PADataType  int32  `asn1:"explicit,tag:1"`
+	PADataValue []byte `asn1:"explicit,tag:2"`
+}
+
+// PADataSequence implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7
+type PADataSequence []PAData
+
+// MethodData implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.9.1
+type MethodData []PAData
+
+// PAEncTimestamp implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.2
+type PAEncTimestamp EncryptedData
+
+// PAEncTSEnc implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.2
+type PAEncTSEnc struct {
+	PATimestamp time.Time `asn1:"generalized,explicit,tag:0"`
+	PAUSec      int       `asn1:"explicit,optional,tag:1"`
+}
+
+// Contains tests if a PADataSequence contains PA Data of a certain type.
+func (pas *PADataSequence) Contains(patype int32) bool {
+	for _, pa := range *pas {
+		if pa.PADataType == patype {
+			return true
+		}
+	}
+	return false
+}
+
+// GetPAEncTSEncAsnMarshalled returns the bytes of a PAEncTSEnc.
+func GetPAEncTSEncAsnMarshalled() ([]byte, error) {
+	t := time.Now().UTC()
+	p := PAEncTSEnc{
+		PATimestamp: t,
+		PAUSec:      int((t.UnixNano() / int64(time.Microsecond)) - (t.Unix() * 1e6)),
+	}
+	b, err := asn1.Marshal(p)
+	if err != nil {
+		return b, fmt.Errorf("error mashaling PAEncTSEnc: %v", err)
+	}
+	return b, nil
+}
+
+// ETypeInfoEntry implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.4
+type ETypeInfoEntry struct {
+	EType int32  `asn1:"explicit,tag:0"`
+	Salt  []byte `asn1:"explicit,optional,tag:1"`
+}
+
+// ETypeInfo implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.4
+type ETypeInfo []ETypeInfoEntry
+
+// ETypeInfo2Entry implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.5
+type ETypeInfo2Entry struct {
+	EType     int32  `asn1:"explicit,tag:0"`
+	Salt      string `asn1:"explicit,optional,generalstring,tag:1"`
+	S2KParams []byte `asn1:"explicit,optional,tag:2"`
+}
+
+// ETypeInfo2 implements RFC 4120 types: https://tools.ietf.org/html/rfc4120#section-5.2.7.5
+type ETypeInfo2 []ETypeInfo2Entry
+
+// PAReqEncPARep PA Data Type
+type PAReqEncPARep struct {
+	ChksumType int32  `asn1:"explicit,tag:0"`
+	Chksum     []byte `asn1:"explicit,tag:1"`
+}
+
+// Unmarshal bytes into the PAData
+func (pa *PAData) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, pa)
+	return err
+}
+
+// Unmarshal bytes into the PADataSequence
+func (pas *PADataSequence) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, pas)
+	return err
+}
+
+// Unmarshal bytes into the PAReqEncPARep
+func (pa *PAReqEncPARep) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, pa)
+	return err
+}
+
+// Unmarshal bytes into the PAEncTimestamp
+func (pa *PAEncTimestamp) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, pa)
+	return err
+}
+
+// Unmarshal bytes into the PAEncTSEnc
+func (pa *PAEncTSEnc) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, pa)
+	return err
+}
+
+// Unmarshal bytes into the ETypeInfo
+func (a *ETypeInfo) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
+
+// Unmarshal bytes into the ETypeInfoEntry
+func (a *ETypeInfoEntry) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
+
+// Unmarshal bytes into the ETypeInfo2
+func (a *ETypeInfo2) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
+
+// Unmarshal bytes into the ETypeInfo2Entry
+func (a *ETypeInfo2Entry) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}
+
+// GetETypeInfo returns an ETypeInfo from the PAData.
+func (pa *PAData) GetETypeInfo() (d ETypeInfo, err error) {
+	if pa.PADataType != patype.PA_ETYPE_INFO {
+		err = fmt.Errorf("PAData does not contain PA EType Info data. TypeID Expected: %v; Actual: %v", patype.PA_ETYPE_INFO, pa.PADataType)
+		return
+	}
+	_, err = asn1.Unmarshal(pa.PADataValue, &d)
+	return
+}
+
+// GetETypeInfo2 returns an ETypeInfo2 from the PAData.
+func (pa *PAData) GetETypeInfo2() (d ETypeInfo2, err error) {
+	if pa.PADataType != patype.PA_ETYPE_INFO2 {
+		err = fmt.Errorf("PAData does not contain PA EType Info 2 data. TypeID Expected: %v; Actual: %v", patype.PA_ETYPE_INFO2, pa.PADataType)
+		return
+	}
+	_, err = asn1.Unmarshal(pa.PADataValue, &d)
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/types/PrincipalName.go b/vendor/github.com/jcmturner/gokrb5/v8/types/PrincipalName.go
new file mode 100644
index 0000000..d48d2c1
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/types/PrincipalName.go
@@ -0,0 +1,67 @@
+package types
+
+import (
+	"strings"
+
+	"github.com/jcmturner/gokrb5/v8/iana/nametype"
+)
+
+// Reference: https://www.ietf.org/rfc/rfc4120.txt
+// Section: 5.2.2
+
+// PrincipalName implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.2.2
+type PrincipalName struct {
+	NameType   int32    `asn1:"explicit,tag:0"`
+	NameString []string `asn1:"generalstring,explicit,tag:1"`
+}
+
+// NewPrincipalName creates a new PrincipalName from the name type int32 and name string provided.
+func NewPrincipalName(ntype int32, spn string) PrincipalName {
+	return PrincipalName{
+		NameType:   ntype,
+		NameString: strings.Split(spn, "/"),
+	}
+}
+
+// GetSalt returns a salt derived from the PrincipalName.
+func (pn PrincipalName) GetSalt(realm string) string {
+	var sb []byte
+	sb = append(sb, realm...)
+	for _, n := range pn.NameString {
+		sb = append(sb, n...)
+	}
+	return string(sb)
+}
+
+// Equal tests if the PrincipalName is equal to the one provided.
+func (pn PrincipalName) Equal(n PrincipalName) bool {
+	if len(pn.NameString) != len(n.NameString) {
+		return false
+	}
+	//https://tools.ietf.org/html/rfc4120#section-6.2 - the name type is not significant when checking for equivalence
+	for i, s := range pn.NameString {
+		if n.NameString[i] != s {
+			return false
+		}
+	}
+	return true
+}
+
+// PrincipalNameString returns the PrincipalName in string form.
+func (pn PrincipalName) PrincipalNameString() string {
+	return strings.Join(pn.NameString, "/")
+}
+
+// ParseSPNString will parse a string in the format <service>/<name>@<realm>
+// a PrincipalName type will be returned with the name type set to KRB_NT_PRINCIPAL(1)
+// and the realm will be returned as a string. If the "@<realm>" suffix
+// is not included in the SPN then the value of realm string returned will be ""
+func ParseSPNString(spn string) (pn PrincipalName, realm string) {
+	if strings.Contains(spn, "@") {
+		s := strings.Split(spn, "@")
+		realm = s[len(s)-1]
+		spn = strings.TrimSuffix(spn, "@"+realm)
+	}
+	pn = NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn)
+	return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/types/TypedData.go b/vendor/github.com/jcmturner/gokrb5/v8/types/TypedData.go
new file mode 100644
index 0000000..19e9f49
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/types/TypedData.go
@@ -0,0 +1,18 @@
+package types
+
+import "github.com/jcmturner/gofork/encoding/asn1"
+
+// TypedData implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.9.1
+type TypedData struct {
+	DataType  int32  `asn1:"explicit,tag:0"`
+	DataValue []byte `asn1:"optional,explicit,tag:1"`
+}
+
+// TypedDataSequence implements RFC 4120 type: https://tools.ietf.org/html/rfc4120#section-5.9.1
+type TypedDataSequence []TypedData
+
+// Unmarshal bytes into the TypedDataSequence.
+func (a *TypedDataSequence) Unmarshal(b []byte) error {
+	_, err := asn1.Unmarshal(b, a)
+	return err
+}