Update module lib/pq to v1.6.0 (#572)
Update module lib/pq to v1.6.0 Reviewed-on: https://kolaente.dev/vikunja/api/pulls/572
This commit is contained in:
373
vendor/github.com/jcmturner/gokrb5/v8/spnego/http.go
generated
vendored
Normal file
373
vendor/github.com/jcmturner/gokrb5/v8/spnego/http.go
generated
vendored
Normal file
@ -0,0 +1,373 @@
|
||||
package spnego
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/jcmturner/gofork/encoding/asn1"
|
||||
"github.com/jcmturner/goidentity/v6"
|
||||
"github.com/jcmturner/gokrb5/v8/client"
|
||||
"github.com/jcmturner/gokrb5/v8/credentials"
|
||||
"github.com/jcmturner/gokrb5/v8/gssapi"
|
||||
"github.com/jcmturner/gokrb5/v8/keytab"
|
||||
"github.com/jcmturner/gokrb5/v8/krberror"
|
||||
"github.com/jcmturner/gokrb5/v8/service"
|
||||
"github.com/jcmturner/gokrb5/v8/types"
|
||||
)
|
||||
|
||||
// Client side functionality //
|
||||
|
||||
// Client will negotiate authentication with a server using SPNEGO.
|
||||
type Client struct {
|
||||
*http.Client
|
||||
krb5Client *client.Client
|
||||
spn string
|
||||
reqs []*http.Request
|
||||
}
|
||||
|
||||
type redirectErr struct {
|
||||
reqTarget *http.Request
|
||||
}
|
||||
|
||||
func (e redirectErr) Error() string {
|
||||
return fmt.Sprintf("redirect to %v", e.reqTarget.URL)
|
||||
}
|
||||
|
||||
type teeReadCloser struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// NewClient returns a SPNEGO enabled HTTP client.
|
||||
// Be careful when passing in the *http.Client if it is beginning reused in multiple calls to this function.
|
||||
// Ensure reuse of the provided *http.Client is for the same user as a session cookie may have been added to
|
||||
// http.Client's cookie jar.
|
||||
// Incorrect reuse of the provided *http.Client could lead to access to the wrong user's session.
|
||||
func NewClient(krb5Cl *client.Client, httpCl *http.Client, spn string) *Client {
|
||||
if httpCl == nil {
|
||||
httpCl = &http.Client{}
|
||||
}
|
||||
// Add a cookie jar if there isn't one
|
||||
if httpCl.Jar == nil {
|
||||
httpCl.Jar, _ = cookiejar.New(nil)
|
||||
}
|
||||
// Add a CheckRedirect function that will execute any functional already defined and then error with a redirectErr
|
||||
f := httpCl.CheckRedirect
|
||||
httpCl.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
if f != nil {
|
||||
err := f(req, via)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return redirectErr{reqTarget: req}
|
||||
}
|
||||
return &Client{
|
||||
Client: httpCl,
|
||||
krb5Client: krb5Cl,
|
||||
spn: spn,
|
||||
}
|
||||
}
|
||||
|
||||
// Do is the SPNEGO enabled HTTP client's equivalent of the http.Client's Do method.
|
||||
func (c *Client) Do(req *http.Request) (resp *http.Response, err error) {
|
||||
var body bytes.Buffer
|
||||
if req.Body != nil {
|
||||
// Use a tee reader to capture any body sent in case we have to replay it again
|
||||
teeR := io.TeeReader(req.Body, &body)
|
||||
teeRC := teeReadCloser{teeR, req.Body}
|
||||
req.Body = teeRC
|
||||
}
|
||||
resp, err = c.Client.Do(req)
|
||||
if err != nil {
|
||||
if ue, ok := err.(*url.Error); ok {
|
||||
if e, ok := ue.Err.(redirectErr); ok {
|
||||
// Picked up a redirect
|
||||
e.reqTarget.Header.Del(HTTPHeaderAuthRequest)
|
||||
c.reqs = append(c.reqs, e.reqTarget)
|
||||
if len(c.reqs) >= 10 {
|
||||
return resp, errors.New("stopped after 10 redirects")
|
||||
}
|
||||
if req.Body != nil {
|
||||
// Refresh the body reader so the body can be sent again
|
||||
e.reqTarget.Body = ioutil.NopCloser(&body)
|
||||
}
|
||||
return c.Do(e.reqTarget)
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
if respUnauthorizedNegotiate(resp) {
|
||||
err := SetSPNEGOHeader(c.krb5Client, req, c.spn)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if req.Body != nil {
|
||||
// Refresh the body reader so the body can be sent again
|
||||
req.Body = ioutil.NopCloser(&body)
|
||||
}
|
||||
return c.Do(req)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Get is the SPNEGO enabled HTTP client's equivalent of the http.Client's Get method.
|
||||
func (c *Client) Get(url string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// Post is the SPNEGO enabled HTTP client's equivalent of the http.Client's Post method.
|
||||
func (c *Client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// PostForm is the SPNEGO enabled HTTP client's equivalent of the http.Client's PostForm method.
|
||||
func (c *Client) PostForm(url string, data url.Values) (resp *http.Response, err error) {
|
||||
return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||
}
|
||||
|
||||
// Head is the SPNEGO enabled HTTP client's equivalent of the http.Client's Head method.
|
||||
func (c *Client) Head(url string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest("HEAD", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
func respUnauthorizedNegotiate(resp *http.Response) bool {
|
||||
if resp.StatusCode == http.StatusUnauthorized {
|
||||
if resp.Header.Get(HTTPHeaderAuthResponse) == HTTPHeaderAuthResponseValueKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetSPNEGOHeader gets the service ticket and sets it as the SPNEGO authorization header on HTTP request object.
|
||||
// To auto generate the SPN from the request object pass a null string "".
|
||||
func SetSPNEGOHeader(cl *client.Client, r *http.Request, spn string) error {
|
||||
if spn == "" {
|
||||
h := strings.TrimSuffix(strings.SplitN(r.URL.Host, ":", 2)[0], ".")
|
||||
name, err := net.LookupCNAME(h)
|
||||
if err == nil {
|
||||
// Underlyng canonical name should be used for SPN
|
||||
h = strings.TrimSuffix(name, ".")
|
||||
}
|
||||
spn = "HTTP/" + h
|
||||
r.Host = h
|
||||
}
|
||||
cl.Log("using SPN %s", spn)
|
||||
s := SPNEGOClient(cl, spn)
|
||||
err := s.AcquireCred()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not acquire client credential: %v", err)
|
||||
}
|
||||
st, err := s.InitSecContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not initialize context: %v", err)
|
||||
}
|
||||
nb, err := st.Marshal()
|
||||
if err != nil {
|
||||
return krberror.Errorf(err, krberror.EncodingError, "could not marshal SPNEGO")
|
||||
}
|
||||
hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb)
|
||||
r.Header.Set(HTTPHeaderAuthRequest, hs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Service side functionality //
|
||||
|
||||
const (
|
||||
// spnegoNegTokenRespKRBAcceptCompleted - The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead.
|
||||
spnegoNegTokenRespKRBAcceptCompleted = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg=="
|
||||
// spnegoNegTokenRespReject - The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead.
|
||||
spnegoNegTokenRespReject = "Negotiate oQcwBaADCgEC"
|
||||
// spnegoNegTokenRespIncompleteKRB5 - Response token specifying incomplete context and KRB5 as the supported mechtype.
|
||||
spnegoNegTokenRespIncompleteKRB5 = "Negotiate oRQwEqADCgEBoQsGCSqGSIb3EgECAg=="
|
||||
// sessionCredentials is the session value key holding the credentials jcmturner/goidentity/Identity object.
|
||||
sessionCredentials = "github.com/jcmturner/gokrb5/v8/sessionCredentials"
|
||||
// ctxCredentials is the SPNEGO context key holding the credentials jcmturner/goidentity/Identity object.
|
||||
ctxCredentials = "github.com/jcmturner/gokrb5/v8/ctxCredentials"
|
||||
// HTTPHeaderAuthRequest is the header that will hold authn/z information.
|
||||
HTTPHeaderAuthRequest = "Authorization"
|
||||
// HTTPHeaderAuthResponse is the header that will hold SPNEGO data from the server.
|
||||
HTTPHeaderAuthResponse = "WWW-Authenticate"
|
||||
// HTTPHeaderAuthResponseValueKey is the key in the auth header for SPNEGO.
|
||||
HTTPHeaderAuthResponseValueKey = "Negotiate"
|
||||
// UnauthorizedMsg is the message returned in the body when authentication fails.
|
||||
UnauthorizedMsg = "Unauthorised.\n"
|
||||
)
|
||||
|
||||
// SPNEGOKRB5Authenticate is a Kerberos SPNEGO authentication HTTP handler wrapper.
|
||||
func SPNEGOKRB5Authenticate(inner http.Handler, kt *keytab.Keytab, settings ...func(*service.Settings)) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Set up the SPNEGO GSS-API mechanism
|
||||
var spnego *SPNEGO
|
||||
h, err := types.GetHostAddress(r.RemoteAddr)
|
||||
if err == nil {
|
||||
// put in this order so that if the user provides a ClientAddress it will override the one here.
|
||||
o := append([]func(*service.Settings){service.ClientAddress(h)}, settings...)
|
||||
spnego = SPNEGOService(kt, o...)
|
||||
} else {
|
||||
spnego = SPNEGOService(kt, settings...)
|
||||
spnego.Log("%s - SPNEGO could not parse client address: %v", r.RemoteAddr, err)
|
||||
}
|
||||
|
||||
// Check if there is a session manager and if there is an already established session for this client
|
||||
id, err := getSessionCredentials(spnego, r)
|
||||
if err == nil && id.Authenticated() {
|
||||
// There is an established session so bypass auth and serve
|
||||
spnego.Log("%s - SPNEGO request served under session %s", r.RemoteAddr, id.SessionID())
|
||||
inner.ServeHTTP(w, goidentity.AddToHTTPRequestContext(&id, r))
|
||||
return
|
||||
}
|
||||
|
||||
st, err := getAuthorizationNegotiationHeaderAsSPNEGOToken(spnego, r, w)
|
||||
if st == nil || err != nil {
|
||||
// response to client and logging handled in function above so just return
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the context token
|
||||
authed, ctx, status := spnego.AcceptSecContext(st)
|
||||
if status.Code != gssapi.StatusComplete && status.Code != gssapi.StatusContinueNeeded {
|
||||
spnegoResponseReject(spnego, w, "%s - SPNEGO validation error: %v", r.RemoteAddr, status)
|
||||
return
|
||||
}
|
||||
if status.Code == gssapi.StatusContinueNeeded {
|
||||
spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO GSS-API continue needed", r.RemoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
if authed {
|
||||
// Authentication successful; get user's credentials from the context
|
||||
id := ctx.Value(ctxCredentials).(*credentials.Credentials)
|
||||
// Create a new session if a session manager has been configured
|
||||
err = newSession(spnego, r, w, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
spnegoResponseAcceptCompleted(spnego, w, "%s %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, id.UserName(), id.Domain())
|
||||
// Add the identity to the context and serve the inner/wrapped handler
|
||||
inner.ServeHTTP(w, goidentity.AddToHTTPRequestContext(id, r))
|
||||
return
|
||||
}
|
||||
// If we get to here we have not authenticationed so just reject
|
||||
spnegoResponseReject(spnego, w, "%s - SPNEGO Kerberos authentication failed", r.RemoteAddr)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func getAuthorizationNegotiationHeaderAsSPNEGOToken(spnego *SPNEGO, r *http.Request, w http.ResponseWriter) (*SPNEGOToken, error) {
|
||||
s := strings.SplitN(r.Header.Get(HTTPHeaderAuthRequest), " ", 2)
|
||||
if len(s) != 2 || s[0] != HTTPHeaderAuthResponseValueKey {
|
||||
// No Authorization header set so return 401 with WWW-Authenticate Negotiate header
|
||||
w.Header().Set(HTTPHeaderAuthResponse, HTTPHeaderAuthResponseValueKey)
|
||||
http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
|
||||
return nil, errors.New("client did not provide a negotiation authorization header")
|
||||
}
|
||||
|
||||
// Decode the header into an SPNEGO context token
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error in base64 decoding negotiation header: %v", err)
|
||||
spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO %v", r.RemoteAddr, err)
|
||||
return nil, err
|
||||
}
|
||||
var st SPNEGOToken
|
||||
err = st.Unmarshal(b)
|
||||
if err != nil {
|
||||
// Check if this is a raw KRB5 context token - issue #347.
|
||||
var k5t KRB5Token
|
||||
if k5t.Unmarshal(b) != nil {
|
||||
err = fmt.Errorf("error in unmarshaling SPNEGO token: %v", err)
|
||||
spnegoNegotiateKRB5MechType(spnego, w, "%s - SPNEGO %v", r.RemoteAddr, err)
|
||||
return nil, err
|
||||
}
|
||||
// Wrap it into an SPNEGO context token
|
||||
st.Init = true
|
||||
st.NegTokenInit = NegTokenInit{
|
||||
MechTypes: []asn1.ObjectIdentifier{k5t.OID},
|
||||
MechTokenBytes: b,
|
||||
}
|
||||
}
|
||||
return &st, nil
|
||||
}
|
||||
|
||||
func getSessionCredentials(spnego *SPNEGO, r *http.Request) (credentials.Credentials, error) {
|
||||
var creds credentials.Credentials
|
||||
// Check if there is a session manager and if there is an already established session for this client
|
||||
if sm := spnego.serviceSettings.SessionManager(); sm != nil {
|
||||
cb, err := sm.Get(r, sessionCredentials)
|
||||
if err != nil || cb == nil || len(cb) < 1 {
|
||||
return creds, fmt.Errorf("%s - SPNEGO error getting session and credentials for request: %v", r.RemoteAddr, err)
|
||||
}
|
||||
err = creds.Unmarshal(cb)
|
||||
if err != nil {
|
||||
return creds, fmt.Errorf("%s - SPNEGO credentials malformed in session: %v", r.RemoteAddr, err)
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
return creds, errors.New("no session manager configured")
|
||||
}
|
||||
|
||||
func newSession(spnego *SPNEGO, r *http.Request, w http.ResponseWriter, id *credentials.Credentials) error {
|
||||
if sm := spnego.serviceSettings.SessionManager(); sm != nil {
|
||||
// create new session
|
||||
idb, err := id.Marshal()
|
||||
if err != nil {
|
||||
spnegoInternalServerError(spnego, w, "SPNEGO could not marshal credentials to add to the session: %v", err)
|
||||
return err
|
||||
}
|
||||
err = sm.New(w, r, sessionCredentials, idb)
|
||||
if err != nil {
|
||||
spnegoInternalServerError(spnego, w, "SPNEGO could not create new session: %v", err)
|
||||
return err
|
||||
}
|
||||
spnego.Log("%s %s@%s - SPNEGO new session (%s) created", r.RemoteAddr, id.UserName(), id.Domain(), id.SessionID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Log and respond to client for error conditions
|
||||
|
||||
func spnegoNegotiateKRB5MechType(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) {
|
||||
s.Log(format, v...)
|
||||
w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespIncompleteKRB5)
|
||||
http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func spnegoResponseReject(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) {
|
||||
s.Log(format, v...)
|
||||
w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespReject)
|
||||
http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func spnegoResponseAcceptCompleted(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) {
|
||||
s.Log(format, v...)
|
||||
w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespKRBAcceptCompleted)
|
||||
}
|
||||
|
||||
func spnegoInternalServerError(s *SPNEGO, w http.ResponseWriter, format string, v ...interface{}) {
|
||||
s.Log(format, v...)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
218
vendor/github.com/jcmturner/gokrb5/v8/spnego/krb5Token.go
generated
vendored
Normal file
218
vendor/github.com/jcmturner/gokrb5/v8/spnego/krb5Token.go
generated
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
package spnego
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jcmturner/gofork/encoding/asn1"
|
||||
"github.com/jcmturner/gokrb5/v8/asn1tools"
|
||||
"github.com/jcmturner/gokrb5/v8/client"
|
||||
"github.com/jcmturner/gokrb5/v8/credentials"
|
||||
"github.com/jcmturner/gokrb5/v8/gssapi"
|
||||
"github.com/jcmturner/gokrb5/v8/iana/chksumtype"
|
||||
"github.com/jcmturner/gokrb5/v8/iana/msgtype"
|
||||
"github.com/jcmturner/gokrb5/v8/krberror"
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
"github.com/jcmturner/gokrb5/v8/service"
|
||||
"github.com/jcmturner/gokrb5/v8/types"
|
||||
)
|
||||
|
||||
// GSSAPI KRB5 MechToken IDs.
|
||||
const (
|
||||
TOK_ID_KRB_AP_REQ = "0100"
|
||||
TOK_ID_KRB_AP_REP = "0200"
|
||||
TOK_ID_KRB_ERROR = "0300"
|
||||
)
|
||||
|
||||
// KRB5Token context token implementation for GSSAPI.
|
||||
type KRB5Token struct {
|
||||
OID asn1.ObjectIdentifier
|
||||
tokID []byte
|
||||
APReq messages.APReq
|
||||
APRep messages.APRep
|
||||
KRBError messages.KRBError
|
||||
settings *service.Settings
|
||||
context context.Context
|
||||
}
|
||||
|
||||
// Marshal a KRB5Token into a slice of bytes.
|
||||
func (m *KRB5Token) Marshal() ([]byte, error) {
|
||||
// Create the header
|
||||
b, _ := asn1.Marshal(m.OID)
|
||||
b = append(b, m.tokID...)
|
||||
var tb []byte
|
||||
var err error
|
||||
switch hex.EncodeToString(m.tokID) {
|
||||
case TOK_ID_KRB_AP_REQ:
|
||||
tb, err = m.APReq.Marshal()
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("error marshalling AP_REQ for MechToken: %v", err)
|
||||
}
|
||||
case TOK_ID_KRB_AP_REP:
|
||||
return []byte{}, errors.New("marshal of AP_REP GSSAPI MechToken not supported by gokrb5")
|
||||
case TOK_ID_KRB_ERROR:
|
||||
return []byte{}, errors.New("marshal of KRB_ERROR GSSAPI MechToken not supported by gokrb5")
|
||||
}
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("error mashalling kerberos message within mech token: %v", err)
|
||||
}
|
||||
b = append(b, tb...)
|
||||
return asn1tools.AddASNAppTag(b, 0), nil
|
||||
}
|
||||
|
||||
// Unmarshal a KRB5Token.
|
||||
func (m *KRB5Token) Unmarshal(b []byte) error {
|
||||
var oid asn1.ObjectIdentifier
|
||||
r, err := asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshalling KRB5Token OID: %v", err)
|
||||
}
|
||||
if !oid.Equal(gssapi.OIDKRB5.OID()) {
|
||||
return fmt.Errorf("error unmarshalling KRB5Token, OID is %s not %s", oid.String(), gssapi.OIDKRB5.OID().String())
|
||||
}
|
||||
m.OID = oid
|
||||
if len(r) < 2 {
|
||||
return fmt.Errorf("krb5token too short")
|
||||
}
|
||||
m.tokID = r[0:2]
|
||||
switch hex.EncodeToString(m.tokID) {
|
||||
case TOK_ID_KRB_AP_REQ:
|
||||
var a messages.APReq
|
||||
err = a.Unmarshal(r[2:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshalling KRB5Token AP_REQ: %v", err)
|
||||
}
|
||||
m.APReq = a
|
||||
case TOK_ID_KRB_AP_REP:
|
||||
var a messages.APRep
|
||||
err = a.Unmarshal(r[2:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshalling KRB5Token AP_REP: %v", err)
|
||||
}
|
||||
m.APRep = a
|
||||
case TOK_ID_KRB_ERROR:
|
||||
var a messages.KRBError
|
||||
err = a.Unmarshal(r[2:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error unmarshalling KRB5Token KRBError: %v", err)
|
||||
}
|
||||
m.KRBError = a
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify a KRB5Token.
|
||||
func (m *KRB5Token) Verify() (bool, gssapi.Status) {
|
||||
switch hex.EncodeToString(m.tokID) {
|
||||
case TOK_ID_KRB_AP_REQ:
|
||||
ok, creds, err := service.VerifyAPREQ(&m.APReq, m.settings)
|
||||
if err != nil {
|
||||
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()}
|
||||
}
|
||||
if !ok {
|
||||
return false, gssapi.Status{Code: gssapi.StatusDefectiveCredential, Message: "KRB5_AP_REQ token not valid"}
|
||||
}
|
||||
m.context = context.Background()
|
||||
m.context = context.WithValue(m.context, ctxCredentials, creds)
|
||||
return true, gssapi.Status{Code: gssapi.StatusComplete}
|
||||
case TOK_ID_KRB_AP_REP:
|
||||
// Client side
|
||||
// TODO how to verify the AP_REP - not yet implemented
|
||||
return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "verifying an AP_REP is not currently supported by gokrb5"}
|
||||
case TOK_ID_KRB_ERROR:
|
||||
if m.KRBError.MsgType != msgtype.KRB_ERROR {
|
||||
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "KRB5_Error token not valid"}
|
||||
}
|
||||
return true, gssapi.Status{Code: gssapi.StatusUnavailable}
|
||||
}
|
||||
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "unknown TOK_ID in KRB5 token"}
|
||||
}
|
||||
|
||||
// IsAPReq tests if the MechToken contains an AP_REQ.
|
||||
func (m *KRB5Token) IsAPReq() bool {
|
||||
if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REQ {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAPRep tests if the MechToken contains an AP_REP.
|
||||
func (m *KRB5Token) IsAPRep() bool {
|
||||
if hex.EncodeToString(m.tokID) == TOK_ID_KRB_AP_REP {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsKRBError tests if the MechToken contains an KRB_ERROR.
|
||||
func (m *KRB5Token) IsKRBError() bool {
|
||||
if hex.EncodeToString(m.tokID) == TOK_ID_KRB_ERROR {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Context returns the KRB5 token's context which will contain any verify user identity information.
|
||||
func (m *KRB5Token) Context() context.Context {
|
||||
return m.context
|
||||
}
|
||||
|
||||
// NewKRB5TokenAPREQ creates a new KRB5 token with AP_REQ
|
||||
func NewKRB5TokenAPREQ(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey, GSSAPIFlags []int, APOptions []int) (KRB5Token, error) {
|
||||
// TODO consider providing the SPN rather than the specific tkt and key and get these from the krb client.
|
||||
var m KRB5Token
|
||||
m.OID = gssapi.OIDKRB5.OID()
|
||||
tb, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ)
|
||||
m.tokID = tb
|
||||
|
||||
auth, err := krb5TokenAuthenticator(cl.Credentials, GSSAPIFlags)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
APReq, err := messages.NewAPReq(
|
||||
tkt,
|
||||
sessionKey,
|
||||
auth,
|
||||
)
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
for _, o := range APOptions {
|
||||
types.SetFlag(&APReq.APOptions, o)
|
||||
}
|
||||
m.APReq = APReq
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// krb5TokenAuthenticator creates a new kerberos authenticator for kerberos MechToken
|
||||
func krb5TokenAuthenticator(creds *credentials.Credentials, flags []int) (types.Authenticator, error) {
|
||||
//RFC 4121 Section 4.1.1
|
||||
auth, err := types.NewAuthenticator(creds.Domain(), creds.CName())
|
||||
if err != nil {
|
||||
return auth, krberror.Errorf(err, krberror.KRBMsgError, "error generating new authenticator")
|
||||
}
|
||||
auth.Cksum = types.Checksum{
|
||||
CksumType: chksumtype.GSSAPI,
|
||||
Checksum: newAuthenticatorChksum(flags),
|
||||
}
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
// Create new authenticator checksum for kerberos MechToken
|
||||
func newAuthenticatorChksum(flags []int) []byte {
|
||||
a := make([]byte, 24)
|
||||
binary.LittleEndian.PutUint32(a[:4], 16)
|
||||
for _, i := range flags {
|
||||
if i == gssapi.ContextFlagDeleg {
|
||||
x := make([]byte, 28-len(a))
|
||||
a = append(a, x...)
|
||||
}
|
||||
f := binary.LittleEndian.Uint32(a[20:24])
|
||||
f |= uint32(i)
|
||||
binary.LittleEndian.PutUint32(a[20:24], f)
|
||||
}
|
||||
return a
|
||||
}
|
302
vendor/github.com/jcmturner/gokrb5/v8/spnego/negotiationToken.go
generated
vendored
Normal file
302
vendor/github.com/jcmturner/gokrb5/v8/spnego/negotiationToken.go
generated
vendored
Normal file
@ -0,0 +1,302 @@
|
||||
package spnego
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jcmturner/gofork/encoding/asn1"
|
||||
"github.com/jcmturner/gokrb5/v8/client"
|
||||
"github.com/jcmturner/gokrb5/v8/gssapi"
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
"github.com/jcmturner/gokrb5/v8/service"
|
||||
"github.com/jcmturner/gokrb5/v8/types"
|
||||
)
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/ms995330.aspx
|
||||
|
||||
// Negotiation state values.
|
||||
const (
|
||||
NegStateAcceptCompleted NegState = 0
|
||||
NegStateAcceptIncomplete NegState = 1
|
||||
NegStateReject NegState = 2
|
||||
NegStateRequestMIC NegState = 3
|
||||
)
|
||||
|
||||
// NegState is a type to indicate the SPNEGO negotiation state.
|
||||
type NegState int
|
||||
|
||||
// NegTokenInit implements Negotiation Token of type Init.
|
||||
type NegTokenInit struct {
|
||||
MechTypes []asn1.ObjectIdentifier
|
||||
ReqFlags gssapi.ContextFlags
|
||||
MechTokenBytes []byte
|
||||
MechListMIC []byte
|
||||
mechToken gssapi.ContextToken
|
||||
settings *service.Settings
|
||||
}
|
||||
|
||||
type marshalNegTokenInit struct {
|
||||
MechTypes []asn1.ObjectIdentifier `asn1:"explicit,tag:0"`
|
||||
ReqFlags gssapi.ContextFlags `asn1:"explicit,optional,tag:1"`
|
||||
MechTokenBytes []byte `asn1:"explicit,optional,omitempty,tag:2"`
|
||||
MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens
|
||||
}
|
||||
|
||||
// NegTokenResp implements Negotiation Token of type Resp/Targ
|
||||
type NegTokenResp struct {
|
||||
NegState asn1.Enumerated
|
||||
SupportedMech asn1.ObjectIdentifier
|
||||
ResponseToken []byte
|
||||
MechListMIC []byte
|
||||
mechToken gssapi.ContextToken
|
||||
settings *service.Settings
|
||||
}
|
||||
|
||||
type marshalNegTokenResp struct {
|
||||
NegState asn1.Enumerated `asn1:"explicit,tag:0"`
|
||||
SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,tag:1"`
|
||||
ResponseToken []byte `asn1:"explicit,optional,omitempty,tag:2"`
|
||||
MechListMIC []byte `asn1:"explicit,optional,omitempty,tag:3"` // This field is not used when negotiating Kerberos tokens
|
||||
}
|
||||
|
||||
// NegTokenTarg implements Negotiation Token of type Resp/Targ
|
||||
type NegTokenTarg NegTokenResp
|
||||
|
||||
// Marshal an Init negotiation token
|
||||
func (n *NegTokenInit) Marshal() ([]byte, error) {
|
||||
m := marshalNegTokenInit{
|
||||
MechTypes: n.MechTypes,
|
||||
ReqFlags: n.ReqFlags,
|
||||
MechTokenBytes: n.MechTokenBytes,
|
||||
MechListMIC: n.MechListMIC,
|
||||
}
|
||||
b, err := asn1.Marshal(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nt := asn1.RawValue{
|
||||
Tag: 0,
|
||||
Class: 2,
|
||||
IsCompound: true,
|
||||
Bytes: b,
|
||||
}
|
||||
nb, err := asn1.Marshal(nt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nb, nil
|
||||
}
|
||||
|
||||
// Unmarshal an Init negotiation token
|
||||
func (n *NegTokenInit) Unmarshal(b []byte) error {
|
||||
init, nt, err := UnmarshalNegToken(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !init {
|
||||
return errors.New("bytes were not that of a NegTokenInit")
|
||||
}
|
||||
nInit := nt.(NegTokenInit)
|
||||
n.MechTokenBytes = nInit.MechTokenBytes
|
||||
n.MechListMIC = nInit.MechListMIC
|
||||
n.MechTypes = nInit.MechTypes
|
||||
n.ReqFlags = nInit.ReqFlags
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify an Init negotiation token
|
||||
func (n *NegTokenInit) Verify() (bool, gssapi.Status) {
|
||||
// Check if supported mechanisms are in the MechTypeList
|
||||
var mtSupported bool
|
||||
for _, m := range n.MechTypes {
|
||||
if m.Equal(gssapi.OIDKRB5.OID()) || m.Equal(gssapi.OIDMSLegacyKRB5.OID()) {
|
||||
if n.mechToken == nil && n.MechTokenBytes == nil {
|
||||
return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
|
||||
}
|
||||
mtSupported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !mtSupported {
|
||||
return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"}
|
||||
}
|
||||
// There should be some mechtoken bytes for a KRB5Token (other mech types are not supported)
|
||||
mt := new(KRB5Token)
|
||||
mt.settings = n.settings
|
||||
if n.mechToken == nil {
|
||||
err := mt.Unmarshal(n.MechTokenBytes)
|
||||
if err != nil {
|
||||
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()}
|
||||
}
|
||||
n.mechToken = mt
|
||||
} else {
|
||||
var ok bool
|
||||
mt, ok = n.mechToken.(*KRB5Token)
|
||||
if !ok {
|
||||
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"}
|
||||
}
|
||||
}
|
||||
// Verify the mechtoken
|
||||
return n.mechToken.Verify()
|
||||
}
|
||||
|
||||
// Context returns the SPNEGO context which will contain any verify user identity information.
|
||||
func (n *NegTokenInit) Context() context.Context {
|
||||
if n.mechToken != nil {
|
||||
mt, ok := n.mechToken.(*KRB5Token)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return mt.Context()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal a Resp/Targ negotiation token
|
||||
func (n *NegTokenResp) Marshal() ([]byte, error) {
|
||||
m := marshalNegTokenResp{
|
||||
NegState: n.NegState,
|
||||
SupportedMech: n.SupportedMech,
|
||||
ResponseToken: n.ResponseToken,
|
||||
MechListMIC: n.MechListMIC,
|
||||
}
|
||||
b, err := asn1.Marshal(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nt := asn1.RawValue{
|
||||
Tag: 1,
|
||||
Class: 2,
|
||||
IsCompound: true,
|
||||
Bytes: b,
|
||||
}
|
||||
nb, err := asn1.Marshal(nt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nb, nil
|
||||
}
|
||||
|
||||
// Unmarshal a Resp/Targ negotiation token
|
||||
func (n *NegTokenResp) Unmarshal(b []byte) error {
|
||||
init, nt, err := UnmarshalNegToken(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if init {
|
||||
return errors.New("bytes were not that of a NegTokenResp")
|
||||
}
|
||||
nResp := nt.(NegTokenResp)
|
||||
n.MechListMIC = nResp.MechListMIC
|
||||
n.NegState = nResp.NegState
|
||||
n.ResponseToken = nResp.ResponseToken
|
||||
n.SupportedMech = nResp.SupportedMech
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify a Resp/Targ negotiation token
|
||||
func (n *NegTokenResp) Verify() (bool, gssapi.Status) {
|
||||
if n.SupportedMech.Equal(gssapi.OIDKRB5.OID()) || n.SupportedMech.Equal(gssapi.OIDMSLegacyKRB5.OID()) {
|
||||
if n.mechToken == nil && n.ResponseToken == nil {
|
||||
return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
|
||||
}
|
||||
mt := new(KRB5Token)
|
||||
mt.settings = n.settings
|
||||
if n.mechToken == nil {
|
||||
err := mt.Unmarshal(n.ResponseToken)
|
||||
if err != nil {
|
||||
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: err.Error()}
|
||||
}
|
||||
n.mechToken = mt
|
||||
} else {
|
||||
var ok bool
|
||||
mt, ok = n.mechToken.(*KRB5Token)
|
||||
if !ok {
|
||||
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "MechToken is not a KRB5 token as expected"}
|
||||
}
|
||||
}
|
||||
if mt == nil {
|
||||
return false, gssapi.Status{Code: gssapi.StatusContinueNeeded}
|
||||
}
|
||||
// Verify the mechtoken
|
||||
return mt.Verify()
|
||||
}
|
||||
return false, gssapi.Status{Code: gssapi.StatusBadMech, Message: "no supported mechanism specified in negotiation"}
|
||||
}
|
||||
|
||||
// State returns the negotiation state of the negotiation response.
|
||||
func (n *NegTokenResp) State() NegState {
|
||||
return NegState(n.NegState)
|
||||
}
|
||||
|
||||
// Context returns the SPNEGO context which will contain any verify user identity information.
|
||||
func (n *NegTokenResp) Context() context.Context {
|
||||
if n.mechToken != nil {
|
||||
mt, ok := n.mechToken.(*KRB5Token)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return mt.Context()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalNegToken umarshals and returns either a NegTokenInit or a NegTokenResp.
|
||||
//
|
||||
// The boolean indicates if the response is a NegTokenInit.
|
||||
// If error is nil and the boolean is false the response is a NegTokenResp.
|
||||
func UnmarshalNegToken(b []byte) (bool, interface{}, error) {
|
||||
var a asn1.RawValue
|
||||
_, err := asn1.Unmarshal(b, &a)
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("error unmarshalling NegotiationToken: %v", err)
|
||||
}
|
||||
switch a.Tag {
|
||||
case 0:
|
||||
var n marshalNegTokenInit
|
||||
_, err = asn1.Unmarshal(a.Bytes, &n)
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Init): %v", a.Tag, err)
|
||||
}
|
||||
nt := NegTokenInit{
|
||||
MechTypes: n.MechTypes,
|
||||
ReqFlags: n.ReqFlags,
|
||||
MechTokenBytes: n.MechTokenBytes,
|
||||
MechListMIC: n.MechListMIC,
|
||||
}
|
||||
return true, nt, nil
|
||||
case 1:
|
||||
var n marshalNegTokenResp
|
||||
_, err = asn1.Unmarshal(a.Bytes, &n)
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("error unmarshalling NegotiationToken type %d (Resp/Targ): %v", a.Tag, err)
|
||||
}
|
||||
nt := NegTokenResp{
|
||||
NegState: n.NegState,
|
||||
SupportedMech: n.SupportedMech,
|
||||
ResponseToken: n.ResponseToken,
|
||||
MechListMIC: n.MechListMIC,
|
||||
}
|
||||
return false, nt, nil
|
||||
default:
|
||||
return false, nil, errors.New("unknown choice type for NegotiationToken")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NewNegTokenInitKRB5 creates new Init negotiation token for Kerberos 5
|
||||
func NewNegTokenInitKRB5(cl *client.Client, tkt messages.Ticket, sessionKey types.EncryptionKey) (NegTokenInit, error) {
|
||||
mt, err := NewKRB5TokenAPREQ(cl, tkt, sessionKey, []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}, []int{})
|
||||
if err != nil {
|
||||
return NegTokenInit{}, fmt.Errorf("error getting KRB5 token; %v", err)
|
||||
}
|
||||
mtb, err := mt.Marshal()
|
||||
if err != nil {
|
||||
return NegTokenInit{}, fmt.Errorf("error marshalling KRB5 token; %v", err)
|
||||
}
|
||||
return NegTokenInit{
|
||||
MechTypes: []asn1.ObjectIdentifier{gssapi.OIDKRB5.OID()},
|
||||
MechTokenBytes: mtb,
|
||||
}, nil
|
||||
}
|
203
vendor/github.com/jcmturner/gokrb5/v8/spnego/spnego.go
generated
vendored
Normal file
203
vendor/github.com/jcmturner/gokrb5/v8/spnego/spnego.go
generated
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
// Package spnego implements the Simple and Protected GSSAPI Negotiation Mechanism for Kerberos authentication.
|
||||
package spnego
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jcmturner/gofork/encoding/asn1"
|
||||
"github.com/jcmturner/gokrb5/v8/asn1tools"
|
||||
"github.com/jcmturner/gokrb5/v8/client"
|
||||
"github.com/jcmturner/gokrb5/v8/gssapi"
|
||||
"github.com/jcmturner/gokrb5/v8/keytab"
|
||||
"github.com/jcmturner/gokrb5/v8/service"
|
||||
)
|
||||
|
||||
// SPNEGO implements the GSS-API mechanism for RFC 4178
|
||||
type SPNEGO struct {
|
||||
serviceSettings *service.Settings
|
||||
client *client.Client
|
||||
spn string
|
||||
}
|
||||
|
||||
// SPNEGOClient configures the SPNEGO mechanism suitable for client side use.
|
||||
func SPNEGOClient(cl *client.Client, spn string) *SPNEGO {
|
||||
s := new(SPNEGO)
|
||||
s.client = cl
|
||||
s.spn = spn
|
||||
s.serviceSettings = service.NewSettings(nil, service.SName(spn))
|
||||
return s
|
||||
}
|
||||
|
||||
// SPNEGOService configures the SPNEGO mechanism suitable for service side use.
|
||||
func SPNEGOService(kt *keytab.Keytab, options ...func(*service.Settings)) *SPNEGO {
|
||||
s := new(SPNEGO)
|
||||
s.serviceSettings = service.NewSettings(kt, options...)
|
||||
return s
|
||||
}
|
||||
|
||||
// OID returns the GSS-API assigned OID for SPNEGO.
|
||||
func (s *SPNEGO) OID() asn1.ObjectIdentifier {
|
||||
return gssapi.OIDSPNEGO.OID()
|
||||
}
|
||||
|
||||
// AcquireCred is the GSS-API method to acquire a client credential via Kerberos for SPNEGO.
|
||||
func (s *SPNEGO) AcquireCred() error {
|
||||
return s.client.AffirmLogin()
|
||||
}
|
||||
|
||||
// InitSecContext is the GSS-API method for the client to a generate a context token to the service via Kerberos.
|
||||
func (s *SPNEGO) InitSecContext() (gssapi.ContextToken, error) {
|
||||
tkt, key, err := s.client.GetServiceTicket(s.spn)
|
||||
if err != nil {
|
||||
return &SPNEGOToken{}, err
|
||||
}
|
||||
negTokenInit, err := NewNegTokenInitKRB5(s.client, tkt, key)
|
||||
if err != nil {
|
||||
return &SPNEGOToken{}, fmt.Errorf("could not create NegTokenInit: %v", err)
|
||||
}
|
||||
return &SPNEGOToken{
|
||||
Init: true,
|
||||
NegTokenInit: negTokenInit,
|
||||
settings: s.serviceSettings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AcceptSecContext is the GSS-API method for the service to verify the context token provided by the client and
|
||||
// establish a context.
|
||||
func (s *SPNEGO) AcceptSecContext(ct gssapi.ContextToken) (bool, context.Context, gssapi.Status) {
|
||||
var ctx context.Context
|
||||
t, ok := ct.(*SPNEGOToken)
|
||||
if !ok {
|
||||
return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "context token provided was not an SPNEGO token"}
|
||||
}
|
||||
t.settings = s.serviceSettings
|
||||
var oid asn1.ObjectIdentifier
|
||||
if t.Init {
|
||||
oid = t.NegTokenInit.MechTypes[0]
|
||||
}
|
||||
if t.Resp {
|
||||
oid = t.NegTokenResp.SupportedMech
|
||||
}
|
||||
if !(oid.Equal(gssapi.OIDKRB5.OID()) || oid.Equal(gssapi.OIDMSLegacyKRB5.OID())) {
|
||||
return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "SPNEGO OID of MechToken is not of type KRB5"}
|
||||
}
|
||||
// Flags in the NegInit must be used t.NegTokenInit.ReqFlags
|
||||
ok, status := t.Verify()
|
||||
ctx = t.Context()
|
||||
return ok, ctx, status
|
||||
}
|
||||
|
||||
// Log will write to the service's logger if it is configured.
|
||||
func (s *SPNEGO) Log(format string, v ...interface{}) {
|
||||
if s.serviceSettings.Logger() != nil {
|
||||
s.serviceSettings.Logger().Output(2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// SPNEGOToken is a GSS-API context token
|
||||
type SPNEGOToken struct {
|
||||
Init bool
|
||||
Resp bool
|
||||
NegTokenInit NegTokenInit
|
||||
NegTokenResp NegTokenResp
|
||||
settings *service.Settings
|
||||
context context.Context
|
||||
}
|
||||
|
||||
// Marshal SPNEGO context token
|
||||
func (s *SPNEGOToken) Marshal() ([]byte, error) {
|
||||
var b []byte
|
||||
if s.Init {
|
||||
hb, _ := asn1.Marshal(gssapi.OIDSPNEGO.OID())
|
||||
tb, err := s.NegTokenInit.Marshal()
|
||||
if err != nil {
|
||||
return b, fmt.Errorf("could not marshal NegTokenInit: %v", err)
|
||||
}
|
||||
b = append(hb, tb...)
|
||||
return asn1tools.AddASNAppTag(b, 0), nil
|
||||
}
|
||||
if s.Resp {
|
||||
b, err := s.NegTokenResp.Marshal()
|
||||
if err != nil {
|
||||
return b, fmt.Errorf("could not marshal NegTokenResp: %v", err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
return b, errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp")
|
||||
}
|
||||
|
||||
// Unmarshal SPNEGO context token
|
||||
func (s *SPNEGOToken) Unmarshal(b []byte) error {
|
||||
var r []byte
|
||||
var err error
|
||||
// We need some data in the array
|
||||
if len(b) < 1 {
|
||||
return fmt.Errorf("provided byte array is empty")
|
||||
}
|
||||
if b[0] != byte(161) {
|
||||
// Not a NegTokenResp/Targ could be a NegTokenInit
|
||||
var oid asn1.ObjectIdentifier
|
||||
r, err = asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("not a valid SPNEGO token: %v", err)
|
||||
}
|
||||
// Check the OID is the SPNEGO OID value
|
||||
SPNEGOOID := gssapi.OIDSPNEGO.OID()
|
||||
if !oid.Equal(SPNEGOOID) {
|
||||
return fmt.Errorf("OID %s does not match SPNEGO OID %s", oid.String(), SPNEGOOID.String())
|
||||
}
|
||||
} else {
|
||||
// Could be a NegTokenResp/Targ
|
||||
r = b
|
||||
}
|
||||
|
||||
_, nt, err := UnmarshalNegToken(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch v := nt.(type) {
|
||||
case NegTokenInit:
|
||||
s.Init = true
|
||||
s.NegTokenInit = v
|
||||
s.NegTokenInit.settings = s.settings
|
||||
case NegTokenResp:
|
||||
s.Resp = true
|
||||
s.NegTokenResp = v
|
||||
s.NegTokenResp.settings = s.settings
|
||||
default:
|
||||
return errors.New("unknown choice type for NegotiationToken")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify the SPNEGOToken
|
||||
func (s *SPNEGOToken) Verify() (bool, gssapi.Status) {
|
||||
if (!s.Init && !s.Resp) || (s.Init && s.Resp) {
|
||||
return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "invalid SPNEGO token, unclear if NegTokenInit or NegTokenResp"}
|
||||
}
|
||||
if s.Init {
|
||||
s.NegTokenInit.settings = s.settings
|
||||
ok, status := s.NegTokenInit.Verify()
|
||||
if ok {
|
||||
s.context = s.NegTokenInit.Context()
|
||||
}
|
||||
return ok, status
|
||||
}
|
||||
if s.Resp {
|
||||
s.NegTokenResp.settings = s.settings
|
||||
ok, status := s.NegTokenResp.Verify()
|
||||
if ok {
|
||||
s.context = s.NegTokenResp.Context()
|
||||
}
|
||||
return ok, status
|
||||
}
|
||||
// should not be possible to get here
|
||||
return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "unable to verify SPNEGO token"}
|
||||
}
|
||||
|
||||
// Context returns the SPNEGO context which will contain any verify user identity information.
|
||||
func (s *SPNEGOToken) Context() context.Context {
|
||||
return s.context
|
||||
}
|
Reference in New Issue
Block a user