Split large cookies
This commit is contained in:
		
							parent
							
								
									6aa35a9ecf
								
							
						
					
					
						commit
						d4b588dbe9
					
				
							
								
								
									
										115
									
								
								oauthproxy.go
								
								
								
								
							
							
						
						
									
										115
									
								
								oauthproxy.go
								
								
								
								
							|  | @ -26,6 +26,10 @@ const ( | |||
| 
 | ||||
| 	httpScheme  = "http" | ||||
| 	httpsScheme = "https" | ||||
| 
 | ||||
| 	// Cookies are limited to 4kb including the length of the cookie name,
 | ||||
| 	// the cookie name can be up to 256 bytes
 | ||||
| 	maxCookieLength = 3840 | ||||
| ) | ||||
| 
 | ||||
| // SignatureHeaders contains the headers to be signed by the hmac algorithm
 | ||||
|  | @ -282,15 +286,100 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e | |||
| 
 | ||||
| // MakeSessionCookie creates an http.Cookie containing the authenticated user's
 | ||||
| // authentication details
 | ||||
| func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie { | ||||
| func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) []*http.Cookie { | ||||
| 	if value != "" { | ||||
| 		value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now) | ||||
| 		if len(value) > 4096 { | ||||
| 			// Cookies cannot be larger than 4kb
 | ||||
| 			log.Printf("WARNING - Cookie Size: %d bytes", len(value)) | ||||
| 	} | ||||
| 	c := p.makeCookie(req, p.CookieName, value, expiration, now) | ||||
| 	if len(c.Value) > 4096 { | ||||
| 		return splitCookie(c) | ||||
| 	} | ||||
| 	return []*http.Cookie{c} | ||||
| } | ||||
| 
 | ||||
| func copyCookie(c *http.Cookie) *http.Cookie { | ||||
| 	return &http.Cookie{ | ||||
| 		Name:       c.Name, | ||||
| 		Value:      c.Value, | ||||
| 		Path:       c.Path, | ||||
| 		Domain:     c.Domain, | ||||
| 		Expires:    c.Expires, | ||||
| 		RawExpires: c.RawExpires, | ||||
| 		MaxAge:     c.MaxAge, | ||||
| 		Secure:     c.Secure, | ||||
| 		HttpOnly:   c.HttpOnly, | ||||
| 		Raw:        c.Raw, | ||||
| 		Unparsed:   c.Unparsed, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // splitCookie reads the full cookie generated to store the session and splits
 | ||||
| // it into a slice of cookies which fit within the 4kb cookie limit indexing
 | ||||
| // the cookies from 0
 | ||||
| func splitCookie(c *http.Cookie) []*http.Cookie { | ||||
| 	if len(c.Value) < maxCookieLength { | ||||
| 		return []*http.Cookie{c} | ||||
| 	} | ||||
| 	cookies := []*http.Cookie{} | ||||
| 	valueBytes := []byte(c.Value) | ||||
| 	count := 0 | ||||
| 	for len(valueBytes) > 0 { | ||||
| 		new := copyCookie(c) | ||||
| 		new.Name = fmt.Sprintf("%s-%d", c.Name, count) | ||||
| 		count++ | ||||
| 		if len(valueBytes) < maxCookieLength { | ||||
| 			new.Value = string(valueBytes) | ||||
| 			valueBytes = []byte{} | ||||
| 		} else { | ||||
| 			newValue := valueBytes[:maxCookieLength] | ||||
| 			valueBytes = valueBytes[maxCookieLength:] | ||||
| 			new.Value = string(newValue) | ||||
| 		} | ||||
| 		cookies = append(cookies, new) | ||||
| 	} | ||||
| 	return cookies | ||||
| } | ||||
| 
 | ||||
| // joinCookies takes a slice of cookies from the request and reconstructs the
 | ||||
| // full session cookie
 | ||||
| func joinCookies(cookies []*http.Cookie) (*http.Cookie, error) { | ||||
| 	if len(cookies) == 0 { | ||||
| 		return nil, fmt.Errorf("list of cookies must be > 0") | ||||
| 	} | ||||
| 	if len(cookies) == 1 { | ||||
| 		return cookies[0], nil | ||||
| 	} | ||||
| 	c := copyCookie(cookies[0]) | ||||
| 	for i := 1; i < len(cookies); i++ { | ||||
| 		c.Value += cookies[i].Value | ||||
| 	} | ||||
| 	c.Name = strings.TrimRight(c.Name, "-0") | ||||
| 	return c, nil | ||||
| } | ||||
| 
 | ||||
| // loadCookie retreieves the sessions state cookie from the http request.
 | ||||
| // If a single cookie is present this will be returned, otherwise it attempts
 | ||||
| // to reconstruct a cookie split up by splitCookie
 | ||||
| func loadCookie(req *http.Request, cookieName string) (*http.Cookie, error) { | ||||
| 	c, err := req.Cookie(cookieName) | ||||
| 	if err == nil { | ||||
| 		return c, nil | ||||
| 	} | ||||
| 	cookies := []*http.Cookie{} | ||||
| 	err = nil | ||||
| 	count := 0 | ||||
| 	for err == nil { | ||||
| 		var c *http.Cookie | ||||
| 		c, err = req.Cookie(fmt.Sprintf("%s-%d", cookieName, count)) | ||||
| 		if err == nil { | ||||
| 			cookies = append(cookies, c) | ||||
| 			count++ | ||||
| 		} | ||||
| 	} | ||||
| 	return p.makeCookie(req, p.CookieName, value, expiration, now) | ||||
| 	if len(cookies) == 0 { | ||||
| 		return nil, fmt.Errorf("Could not find cookie %s", cookieName) | ||||
| 	} | ||||
| 	return joinCookies(cookies) | ||||
| } | ||||
| 
 | ||||
| // MakeCSRFCookie creates a cookie for CSRF
 | ||||
|  | @ -334,12 +423,14 @@ func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, va | |||
| // ClearSessionCookie creates a cookie to unset the user's authentication cookie
 | ||||
| // stored in the user's session
 | ||||
| func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) { | ||||
| 	clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now()) | ||||
| 	http.SetCookie(rw, clr) | ||||
| 	cookies := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now()) | ||||
| 	for _, clr := range cookies { | ||||
| 		http.SetCookie(rw, clr) | ||||
| 	} | ||||
| 
 | ||||
| 	// ugly hack because default domain changed
 | ||||
| 	if p.CookieDomain == "" { | ||||
| 		clr2 := *clr | ||||
| 	if p.CookieDomain == "" && len(cookies) > 0 { | ||||
| 		clr2 := *cookies[0] | ||||
| 		clr2.Domain = req.Host | ||||
| 		http.SetCookie(rw, &clr2) | ||||
| 	} | ||||
|  | @ -347,13 +438,15 @@ func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Reques | |||
| 
 | ||||
| // SetSessionCookie adds the user's session cookie to the response
 | ||||
| func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) { | ||||
| 	http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now())) | ||||
| 	for _, c := range p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()) { | ||||
| 		http.SetCookie(rw, c) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // LoadCookiedSession reads the user's authentication details from the request
 | ||||
| func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) { | ||||
| 	var age time.Duration | ||||
| 	c, err := req.Cookie(p.CookieName) | ||||
| 	c, err := loadCookie(req, p.CookieName) | ||||
| 	if err != nil { | ||||
| 		// always http.ErrNoCookie
 | ||||
| 		return nil, age, fmt.Errorf("Cookie %q not present", p.CookieName) | ||||
|  |  | |||
|  | @ -502,7 +502,7 @@ func NewProcessCookieTestWithDefaults() *ProcessCookieTest { | |||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func (p *ProcessCookieTest) MakeCookie(value string, ref time.Time) *http.Cookie { | ||||
| func (p *ProcessCookieTest) MakeCookie(value string, ref time.Time) []*http.Cookie { | ||||
| 	return p.proxy.MakeSessionCookie(p.req, value, p.opts.CookieExpire, ref) | ||||
| } | ||||
| 
 | ||||
|  | @ -511,7 +511,9 @@ func (p *ProcessCookieTest) SaveSession(s *providers.SessionState, ref time.Time | |||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	p.req.AddCookie(p.proxy.MakeSessionCookie(p.req, value, p.proxy.CookieExpire, ref)) | ||||
| 	for _, c := range p.proxy.MakeSessionCookie(p.req, value, p.proxy.CookieExpire, ref) { | ||||
| 		p.req.AddCookie(c) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -800,8 +802,9 @@ func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) { | |||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	cookie := proxy.MakeSessionCookie(req, value, proxy.CookieExpire, time.Now()) | ||||
| 	req.AddCookie(cookie) | ||||
| 	for _, c := range proxy.MakeSessionCookie(req, value, proxy.CookieExpire, time.Now()) { | ||||
| 		req.AddCookie(c) | ||||
| 	} | ||||
| 	// This is used by the upstream to validate the signature.
 | ||||
| 	st.authenticator.auth = hmacauth.NewHmacAuth( | ||||
| 		crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue