github.com/jcmturner/gokrb5/v8@v8.4.4/spnego/spnego.go (about) 1 // Package spnego implements the Simple and Protected GSSAPI Negotiation Mechanism for Kerberos authentication. 2 package spnego 3 4 import ( 5 "context" 6 "errors" 7 "fmt" 8 9 "github.com/jcmturner/gofork/encoding/asn1" 10 "github.com/jcmturner/gokrb5/v8/asn1tools" 11 "github.com/jcmturner/gokrb5/v8/client" 12 "github.com/jcmturner/gokrb5/v8/gssapi" 13 "github.com/jcmturner/gokrb5/v8/keytab" 14 "github.com/jcmturner/gokrb5/v8/service" 15 ) 16 17 // SPNEGO implements the GSS-API mechanism for RFC 4178 18 type SPNEGO struct { 19 serviceSettings *service.Settings 20 client *client.Client 21 spn string 22 } 23 24 // SPNEGOClient configures the SPNEGO mechanism suitable for client side use. 25 func SPNEGOClient(cl *client.Client, spn string) *SPNEGO { 26 s := new(SPNEGO) 27 s.client = cl 28 s.spn = spn 29 s.serviceSettings = service.NewSettings(nil, service.SName(spn)) 30 return s 31 } 32 33 // SPNEGOService configures the SPNEGO mechanism suitable for service side use. 34 func SPNEGOService(kt *keytab.Keytab, options ...func(*service.Settings)) *SPNEGO { 35 s := new(SPNEGO) 36 s.serviceSettings = service.NewSettings(kt, options...) 37 return s 38 } 39 40 // OID returns the GSS-API assigned OID for SPNEGO. 41 func (s *SPNEGO) OID() asn1.ObjectIdentifier { 42 return gssapi.OIDSPNEGO.OID() 43 } 44 45 // AcquireCred is the GSS-API method to acquire a client credential via Kerberos for SPNEGO. 46 func (s *SPNEGO) AcquireCred() error { 47 return s.client.AffirmLogin() 48 } 49 50 // InitSecContext is the GSS-API method for the client to a generate a context token to the service via Kerberos. 51 func (s *SPNEGO) InitSecContext() (gssapi.ContextToken, error) { 52 tkt, key, err := s.client.GetServiceTicket(s.spn) 53 if err != nil { 54 return &SPNEGOToken{}, err 55 } 56 negTokenInit, err := NewNegTokenInitKRB5(s.client, tkt, key) 57 if err != nil { 58 return &SPNEGOToken{}, fmt.Errorf("could not create NegTokenInit: %v", err) 59 } 60 return &SPNEGOToken{ 61 Init: true, 62 NegTokenInit: negTokenInit, 63 settings: s.serviceSettings, 64 }, nil 65 } 66 67 // AcceptSecContext is the GSS-API method for the service to verify the context token provided by the client and 68 // establish a context. 69 func (s *SPNEGO) AcceptSecContext(ct gssapi.ContextToken) (bool, context.Context, gssapi.Status) { 70 var ctx context.Context 71 t, ok := ct.(*SPNEGOToken) 72 if !ok { 73 return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "context token provided was not an SPNEGO token"} 74 } 75 t.settings = s.serviceSettings 76 var oid asn1.ObjectIdentifier 77 if t.Init { 78 oid = t.NegTokenInit.MechTypes[0] 79 } 80 if t.Resp { 81 oid = t.NegTokenResp.SupportedMech 82 } 83 if !(oid.Equal(gssapi.OIDKRB5.OID()) || oid.Equal(gssapi.OIDMSLegacyKRB5.OID())) { 84 return false, ctx, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "SPNEGO OID of MechToken is not of type KRB5"} 85 } 86 // Flags in the NegInit must be used t.NegTokenInit.ReqFlags 87 ok, status := t.Verify() 88 ctx = t.Context() 89 return ok, ctx, status 90 } 91 92 // Log will write to the service's logger if it is configured. 93 func (s *SPNEGO) Log(format string, v ...interface{}) { 94 if s.serviceSettings.Logger() != nil { 95 s.serviceSettings.Logger().Output(2, fmt.Sprintf(format, v...)) 96 } 97 } 98 99 // SPNEGOToken is a GSS-API context token 100 type SPNEGOToken struct { 101 Init bool 102 Resp bool 103 NegTokenInit NegTokenInit 104 NegTokenResp NegTokenResp 105 settings *service.Settings 106 context context.Context 107 } 108 109 // Marshal SPNEGO context token 110 func (s *SPNEGOToken) Marshal() ([]byte, error) { 111 var b []byte 112 if s.Init { 113 hb, _ := asn1.Marshal(gssapi.OIDSPNEGO.OID()) 114 tb, err := s.NegTokenInit.Marshal() 115 if err != nil { 116 return b, fmt.Errorf("could not marshal NegTokenInit: %v", err) 117 } 118 b = append(hb, tb...) 119 return asn1tools.AddASNAppTag(b, 0), nil 120 } 121 if s.Resp { 122 b, err := s.NegTokenResp.Marshal() 123 if err != nil { 124 return b, fmt.Errorf("could not marshal NegTokenResp: %v", err) 125 } 126 return b, nil 127 } 128 return b, errors.New("SPNEGO cannot be marshalled. It contains neither a NegTokenInit or NegTokenResp") 129 } 130 131 // Unmarshal SPNEGO context token 132 func (s *SPNEGOToken) Unmarshal(b []byte) error { 133 var r []byte 134 var err error 135 // We need some data in the array 136 if len(b) < 1 { 137 return fmt.Errorf("provided byte array is empty") 138 } 139 if b[0] != byte(161) { 140 // Not a NegTokenResp/Targ could be a NegTokenInit 141 var oid asn1.ObjectIdentifier 142 r, err = asn1.UnmarshalWithParams(b, &oid, fmt.Sprintf("application,explicit,tag:%v", 0)) 143 if err != nil { 144 return fmt.Errorf("not a valid SPNEGO token: %v", err) 145 } 146 // Check the OID is the SPNEGO OID value 147 SPNEGOOID := gssapi.OIDSPNEGO.OID() 148 if !oid.Equal(SPNEGOOID) { 149 return fmt.Errorf("OID %s does not match SPNEGO OID %s", oid.String(), SPNEGOOID.String()) 150 } 151 } else { 152 // Could be a NegTokenResp/Targ 153 r = b 154 } 155 156 _, nt, err := UnmarshalNegToken(r) 157 if err != nil { 158 return err 159 } 160 switch v := nt.(type) { 161 case NegTokenInit: 162 s.Init = true 163 s.NegTokenInit = v 164 s.NegTokenInit.settings = s.settings 165 case NegTokenResp: 166 s.Resp = true 167 s.NegTokenResp = v 168 s.NegTokenResp.settings = s.settings 169 default: 170 return errors.New("unknown choice type for NegotiationToken") 171 } 172 return nil 173 } 174 175 // Verify the SPNEGOToken 176 func (s *SPNEGOToken) Verify() (bool, gssapi.Status) { 177 if (!s.Init && !s.Resp) || (s.Init && s.Resp) { 178 return false, gssapi.Status{Code: gssapi.StatusDefectiveToken, Message: "invalid SPNEGO token, unclear if NegTokenInit or NegTokenResp"} 179 } 180 if s.Init { 181 s.NegTokenInit.settings = s.settings 182 ok, status := s.NegTokenInit.Verify() 183 if ok { 184 s.context = s.NegTokenInit.Context() 185 } 186 return ok, status 187 } 188 if s.Resp { 189 s.NegTokenResp.settings = s.settings 190 ok, status := s.NegTokenResp.Verify() 191 if ok { 192 s.context = s.NegTokenResp.Context() 193 } 194 return ok, status 195 } 196 // should not be possible to get here 197 return false, gssapi.Status{Code: gssapi.StatusFailure, Message: "unable to verify SPNEGO token"} 198 } 199 200 // Context returns the SPNEGO context which will contain any verify user identity information. 201 func (s *SPNEGOToken) Context() context.Context { 202 return s.context 203 }