// Package spnego implements the Simple and Protected GSSAPI Negotiation Mechanism for Kerberos authentication.
package spnego import ( ) // 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 ( *client.Client, string) *SPNEGO { := new(SPNEGO) .client = .spn = .serviceSettings = service.NewSettings(nil, service.SName()) return } // SPNEGOService configures the SPNEGO mechanism suitable for service side use. func ( *keytab.Keytab, ...func(*service.Settings)) *SPNEGO { := new(SPNEGO) .serviceSettings = service.NewSettings(, ...) return } // OID returns the GSS-API assigned OID for SPNEGO. func ( *SPNEGO) () asn1.ObjectIdentifier { return gssapi.OIDSPNEGO.OID() } // AcquireCred is the GSS-API method to acquire a client credential via Kerberos for SPNEGO. func ( *SPNEGO) () error { return .client.AffirmLogin() } // InitSecContext is the GSS-API method for the client to a generate a context token to the service via Kerberos. func ( *SPNEGO) () (gssapi.ContextToken, error) { , , := .client.GetServiceTicket(.spn) if != nil { return &SPNEGOToken{}, } , := NewNegTokenInitKRB5(.client, , ) if != nil { return &SPNEGOToken{}, fmt.Errorf("could not create NegTokenInit: %v", ) } return &SPNEGOToken{ Init: true, NegTokenInit: , settings: .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 ( *SPNEGO) ( gssapi.ContextToken) (bool, context.Context, gssapi.Status) { var context.Context , := .(*SPNEGOToken) if ! { return false, , gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "context token provided was not an SPNEGO token"} } .settings = .serviceSettings var asn1.ObjectIdentifier if .Init { = .NegTokenInit.MechTypes[0] } if .Resp { = .NegTokenResp.SupportedMech } if !(.Equal(gssapi.OIDKRB5.OID()) || .Equal(gssapi.OIDMSLegacyKRB5.OID())) { return false, , 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 , := .Verify() = .Context() return , , } // Log will write to the service's logger if it is configured. func ( *SPNEGO) ( string, ...interface{}) { if .serviceSettings.Logger() != nil { .serviceSettings.Logger().Output(2, fmt.Sprintf(, ...)) } } // 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 ( *SPNEGOToken) () ([]byte, error) { var []byte if .Init { , := asn1.Marshal(gssapi.OIDSPNEGO.OID()) , := .NegTokenInit.Marshal() if != nil { return , fmt.Errorf("could not marshal NegTokenInit: %v", ) } = append(, ...) return asn1tools.AddASNAppTag(, 0), nil } if .Resp { , := .NegTokenResp.Marshal() if != nil { return , fmt.Errorf("could not marshal NegTokenResp: %v", ) } return , nil } return , errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp") } // Unmarshal SPNEGO context token func ( *SPNEGOToken) ( []byte) error { var []byte var error // We need some data in the array if len() < 1 { return fmt.Errorf("provided byte array is empty") } if [0] != byte(161) { // Not a NegTokenResp/Targ could be a NegTokenInit var asn1.ObjectIdentifier , = asn1.UnmarshalWithParams(, &, fmt.Sprintf("application,explicit,tag:%v", 0)) if != nil { return fmt.Errorf("not a valid SPNEGO token: %v", ) } // Check the OID is the SPNEGO OID value := gssapi.OIDSPNEGO.OID() if !.Equal() { return fmt.Errorf("OID %s does not match SPNEGO OID %s", .String(), .String()) } } else { // Could be a NegTokenResp/Targ = } , , := UnmarshalNegToken() if != nil { return } switch v := .(type) { case NegTokenInit: .Init = true .NegTokenInit = .NegTokenInit.settings = .settings case NegTokenResp: .Resp = true .NegTokenResp = .NegTokenResp.settings = .settings default: return errors.New("unknown choice type for NegotiationToken") } return nil } // Verify the SPNEGOToken func ( *SPNEGOToken) () (bool, gssapi.Status) { if (!.Init && !.Resp) || (.Init && .Resp) { return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "invalid SPNEGO token, unclear if NegTokenInit or NegTokenResp"} } if .Init { .NegTokenInit.settings = .settings , := .NegTokenInit.Verify() if { .context = .NegTokenInit.Context() } return , } if .Resp { .NegTokenResp.settings = .settings , := .NegTokenResp.Verify() if { .context = .NegTokenResp.Context() } return , } // 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 ( *SPNEGOToken) () context.Context { return .context }