git.iko.soy/typeable/oauth2-proxy
on master help sign in
base64 cookie support
cdebfd6Jehiah Czebotar
cookie/cookies.go+2−2
@@ -85,8 +85,8 @@ type Cipher struct {
85}
85}
86
86
87// NewCipher returns a new aes Cipher for encrypting cookie values
87// NewCipher returns a new aes Cipher for encrypting cookie values
88−func NewCipher(secret string) (*Cipher, error) {
88+func NewCipher(secret []byte) (*Cipher, error) {
89− c, err := aes.NewCipher([]byte(secret))
89+ c, err := aes.NewCipher(secret)
90 if err != nil {
90 if err != nil {
91 return nil, err
91 return nil, err
92 }
92 }
cookie/cookies_test.go+20−1
@@ -1,6 +1,7 @@
1package cookie
1package cookie
2
2
3import (
3import (
4+ "encoding/base64"
4 "testing"
5 "testing"
5
6
6 "github.com/bmizerany/assert"
7 "github.com/bmizerany/assert"
@@ -9,7 +10,25 @@ import (
9func TestEncodeAndDecodeAccessToken(t *testing.T) {
10func TestEncodeAndDecodeAccessToken(t *testing.T) {
10 const secret = "0123456789abcdefghijklmnopqrstuv"
11 const secret = "0123456789abcdefghijklmnopqrstuv"
11 const token = "my access token"
12 const token = "my access token"
12− c, err := NewCipher(secret)
13+ c, err := NewCipher([]byte(secret))
14+ assert.Equal(t, nil, err)
15+
16+ encoded, err := c.Encrypt(token)
17+ assert.Equal(t, nil, err)
18+
19+ decoded, err := c.Decrypt(encoded)
20+ assert.Equal(t, nil, err)
21+
22+ assert.NotEqual(t, token, encoded)
23+ assert.Equal(t, token, decoded)
24+}
25+
26+func TestEncodeAndDecodeAccessTokenB64(t *testing.T) {
27+ const secret_b64 = "A3Xbr6fu6Al0HkgrP1ztjb-mYiwmxgNPP-XbNsz1WBk="
28+ const token = "my access token"
29+
30+ secret, err := base64.URLEncoding.DecodeString(secret_b64)
31+ c, err := NewCipher([]byte(secret))
13 assert.Equal(t, nil, err)
32 assert.Equal(t, nil, err)
14
33
15 encoded, err := c.Encrypt(token)
34 encoded, err := c.Encrypt(token)
main.go+1−1
@@ -55,7 +55,7 @@ func main() {
55 flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
55 flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
56
56
57 flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
57 flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
58− flagSet.String("cookie-secret", "", "the seed string for secure cookies")
58+ flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)")
59 flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*")
59 flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*")
60 flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
60 flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
61 flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
61 flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
oauthproxy.go+4−5
@@ -1,7 +1,7 @@
1package main
1package main
2
2
3import (
3import (
4− "encoding/base64"
4+ b64 "encoding/base64"
5 "errors"
5 "errors"
6 "fmt"
6 "fmt"
7 "html/template"
7 "html/template"
@@ -164,10 +164,9 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
164 var cipher *cookie.Cipher
164 var cipher *cookie.Cipher
165 if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
165 if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
166 var err error
166 var err error
167− cipher, err = cookie.NewCipher(opts.CookieSecret)
167+ cipher, err = cookie.NewCipher(secretBytes(opts.CookieSecret))
168 if err != nil {
168 if err != nil {
169− log.Fatal("error creating AES cipher with "+
170− "cookie-secret ", opts.CookieSecret, ": ", err)
169+ log.Fatal("cookie-secret error: ", err)
171 }
170 }
172 }
171 }
173
172
@@ -626,7 +625,7 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState,
626 if len(s) != 2 || s[0] != "Basic" {
625 if len(s) != 2 || s[0] != "Basic" {
627 return nil, fmt.Errorf("invalid Authorization header %s", req.Header.Get("Authorization"))
626 return nil, fmt.Errorf("invalid Authorization header %s", req.Header.Get("Authorization"))
628 }
627 }
629− b, err := base64.StdEncoding.DecodeString(s[1])
628+ b, err := b64.StdEncoding.DecodeString(s[1])
630 if err != nil {
629 if err != nil {
631 return nil, err
630 return nil, err
632 }
631 }
oauthproxy_test.go+1−1
@@ -427,7 +427,7 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
427 pc_test.opts = NewOptions()
427 pc_test.opts = NewOptions()
428 pc_test.opts.ClientID = "bazquux"
428 pc_test.opts.ClientID = "bazquux"
429 pc_test.opts.ClientSecret = "xyzzyplugh"
429 pc_test.opts.ClientSecret = "xyzzyplugh"
430− pc_test.opts.CookieSecret = "0123456789abcdef"
430+ pc_test.opts.CookieSecret = "0123456789abcdefabcd"
431 // First, set the CookieRefresh option so proxy.AesCipher is created,
431 // First, set the CookieRefresh option so proxy.AesCipher is created,
432 // needed to encrypt the access_token.
432 // needed to encrypt the access_token.
433 pc_test.opts.CookieRefresh = time.Hour
433 pc_test.opts.CookieRefresh = time.Hour
options.go+35−3
@@ -2,6 +2,7 @@ package main
2
2
3import (
3import (
4 "crypto"
4 "crypto"
5+ "encoding/base64"
5 "fmt"
6 "fmt"
6 "net/url"
7 "net/url"
7 "os"
8 "os"
@@ -156,17 +157,25 @@ func (o *Options) Validate() error {
156 if o.PassAccessToken || (o.CookieRefresh != time.Duration(0)) {
157 if o.PassAccessToken || (o.CookieRefresh != time.Duration(0)) {
157 valid_cookie_secret_size := false
158 valid_cookie_secret_size := false
158 for _, i := range []int{16, 24, 32} {
159 for _, i := range []int{16, 24, 32} {
159− if len(o.CookieSecret) == i {
160+ if len(secretBytes(o.CookieSecret)) == i {
160 valid_cookie_secret_size = true
161 valid_cookie_secret_size = true
161 }
162 }
162 }
163 }
164+ var decoded bool
165+ if string(secretBytes(o.CookieSecret)) != o.CookieSecret {
166+ decoded = true
167+ }
163 if valid_cookie_secret_size == false {
168 if valid_cookie_secret_size == false {
169+ var suffix string
170+ if decoded {
171+ suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.CookieSecret)
172+ }
164 msgs = append(msgs, fmt.Sprintf(
173 msgs = append(msgs, fmt.Sprintf(
165 "cookie_secret must be 16, 24, or 32 bytes "+
174 "cookie_secret must be 16, 24, or 32 bytes "+
166 "to create an AES cipher when "+
175 "to create an AES cipher when "+
167 "pass_access_token == true or "+
176 "pass_access_token == true or "+
168− "cookie_refresh != 0, but is %d bytes",
177+ "cookie_refresh != 0, but is %d bytes.%s",
169− len(o.CookieSecret)))
178+ len(secretBytes(o.CookieSecret)), suffix))
170 }
179 }
171 }
180 }
172
181
@@ -251,3 +260,26 @@ func parseSignatureKey(o *Options, msgs []string) []string {
251 }
260 }
252 return msgs
261 return msgs
253}
262}
263+
264+func addPadding(secret string) string {
265+ padding := len(secret) % 4
266+ switch padding {
267+ case 1:
268+ return secret + "==="
269+ case 2:
270+ return secret + "=="
271+ case 3:
272+ return secret + "="
273+ default:
274+ return secret
275+ }
276+}
277+
278+// secretBytes attempts to base64 decode the secret, if that fails it treats the secret as binary
279+func secretBytes(secret string) []byte {
280+ b, err := base64.URLEncoding.DecodeString(addPadding(secret))
281+ if err == nil {
282+ return []byte(addPadding(string(b)))
283+ }
284+ return []byte(secret)
285+}
options_test.go+26−1
@@ -160,7 +160,7 @@ func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
160 o := testOptions()
160 o := testOptions()
161 assert.Equal(t, nil, o.Validate())
161 assert.Equal(t, nil, o.Validate())
162
162
163− o.CookieSecret = "0123456789abcdef"
163+ o.CookieSecret = "0123456789abcdefabcd"
164 o.CookieRefresh = o.CookieExpire
164 o.CookieRefresh = o.CookieExpire
165 assert.NotEqual(t, nil, o.Validate())
165 assert.NotEqual(t, nil, o.Validate())
166
166
@@ -168,6 +168,31 @@ func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
168 assert.Equal(t, nil, o.Validate())
168 assert.Equal(t, nil, o.Validate())
169}
169}
170
170
171+func TestBase64CookieSecret(t *testing.T) {
172+ o := testOptions()
173+ assert.Equal(t, nil, o.Validate())
174+
175+ // 32 byte, base64 (urlsafe) encoded key
176+ o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
177+ assert.Equal(t, nil, o.Validate())
178+
179+ // 32 byte, base64 (urlsafe) encoded key, w/o padding
180+ o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
181+ assert.Equal(t, nil, o.Validate())
182+
183+ // 24 byte, base64 (urlsafe) encoded key
184+ o.CookieSecret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
185+ assert.Equal(t, nil, o.Validate())
186+
187+ // 16 byte, base64 (urlsafe) encoded key
188+ o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA=="
189+ assert.Equal(t, nil, o.Validate())
190+
191+ // 16 byte, base64 (urlsafe) encoded key, w/o padding
192+ o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA"
193+ assert.Equal(t, nil, o.Validate())
194+}
195+
171func TestValidateSignatureKey(t *testing.T) {
196func TestValidateSignatureKey(t *testing.T) {
172 o := testOptions()
197 o := testOptions()
173 o.SignatureKey = "sha1:secret"
198 o.SignatureKey = "sha1:secret"
providers/github.go+2−2
@@ -3,11 +3,11 @@ package providers
3import (
3import (
4 "encoding/json"
4 "encoding/json"
5 "fmt"
5 "fmt"
6− "strings"
7 "io/ioutil"
6 "io/ioutil"
8 "log"
7 "log"
9 "net/http"
8 "net/http"
10 "net/url"
9 "net/url"
10+ "strings"
11)
11)
12
12
13type GitHubProvider struct {
13type GitHubProvider struct {
@@ -64,7 +64,7 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
64 "limit": {"100"},
64 "limit": {"100"},
65 }
65 }
66
66
67− endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/orgs?" + params.Encode()
67+ endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/orgs?" + params.Encode()
68 req, _ := http.NewRequest("GET", endpoint, nil)
68 req, _ := http.NewRequest("GET", endpoint, nil)
69 req.Header.Set("Accept", "application/vnd.github.v3+json")
69 req.Header.Set("Accept", "application/vnd.github.v3+json")
70 resp, err := http.DefaultClient.Do(req)
70 resp, err := http.DefaultClient.Do(req)
providers/session_state_test.go+2−2
@@ -13,9 +13,9 @@ const secret = "0123456789abcdefghijklmnopqrstuv"
13const altSecret = "0000000000abcdefghijklmnopqrstuv"
13const altSecret = "0000000000abcdefghijklmnopqrstuv"
14
14
15func TestSessionStateSerialization(t *testing.T) {
15func TestSessionStateSerialization(t *testing.T) {
16− c, err := cookie.NewCipher(secret)
16+ c, err := cookie.NewCipher([]byte(secret))
17 assert.Equal(t, nil, err)
17 assert.Equal(t, nil, err)
18− c2, err := cookie.NewCipher(altSecret)
18+ c2, err := cookie.NewCipher([]byte(altSecret))
19 assert.Equal(t, nil, err)
19 assert.Equal(t, nil, err)
20 s := &SessionState{
20 s := &SessionState{
21 Email: "user@domain.com",
21 Email: "user@domain.com",