Skip to content

Commit cdebfd6

Browse files
committed
base64 cookie support
1 parent 57f82ed commit cdebfd6

File tree

9 files changed

+93
-18
lines changed

9 files changed

+93
-18
lines changed

cookie/cookies.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ type Cipher struct {
8585
}
8686

8787
// NewCipher returns a new aes Cipher for encrypting cookie values
88-
func NewCipher(secret string) (*Cipher, error) {
89-
c, err := aes.NewCipher([]byte(secret))
88+
func NewCipher(secret []byte) (*Cipher, error) {
89+
c, err := aes.NewCipher(secret)
9090
if err != nil {
9191
return nil, err
9292
}

cookie/cookies_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cookie
22

33
import (
4+
"encoding/base64"
45
"testing"
56

67
"github.com/bmizerany/assert"
@@ -9,7 +10,25 @@ import (
910
func TestEncodeAndDecodeAccessToken(t *testing.T) {
1011
const secret = "0123456789abcdefghijklmnopqrstuv"
1112
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))
1332
assert.Equal(t, nil, err)
1433

1534
encoded, err := c.Encrypt(token)

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func main() {
5555
flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
5656

5757
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)")
5959
flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*")
6060
flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
6161
flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")

oauthproxy.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package main
22

33
import (
4-
"encoding/base64"
4+
b64 "encoding/base64"
55
"errors"
66
"fmt"
77
"html/template"
@@ -164,10 +164,9 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
164164
var cipher *cookie.Cipher
165165
if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
166166
var err error
167-
cipher, err = cookie.NewCipher(opts.CookieSecret)
167+
cipher, err = cookie.NewCipher(secretBytes(opts.CookieSecret))
168168
if err != nil {
169-
log.Fatal("error creating AES cipher with "+
170-
"cookie-secret ", opts.CookieSecret, ": ", err)
169+
log.Fatal("cookie-secret error: ", err)
171170
}
172171
}
173172

@@ -626,7 +625,7 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState,
626625
if len(s) != 2 || s[0] != "Basic" {
627626
return nil, fmt.Errorf("invalid Authorization header %s", req.Header.Get("Authorization"))
628627
}
629-
b, err := base64.StdEncoding.DecodeString(s[1])
628+
b, err := b64.StdEncoding.DecodeString(s[1])
630629
if err != nil {
631630
return nil, err
632631
}

oauthproxy_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
427427
pc_test.opts = NewOptions()
428428
pc_test.opts.ClientID = "bazquux"
429429
pc_test.opts.ClientSecret = "xyzzyplugh"
430-
pc_test.opts.CookieSecret = "0123456789abcdef"
430+
pc_test.opts.CookieSecret = "0123456789abcdefabcd"
431431
// First, set the CookieRefresh option so proxy.AesCipher is created,
432432
// needed to encrypt the access_token.
433433
pc_test.opts.CookieRefresh = time.Hour

options.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"crypto"
5+
"encoding/base64"
56
"fmt"
67
"net/url"
78
"os"
@@ -156,17 +157,25 @@ func (o *Options) Validate() error {
156157
if o.PassAccessToken || (o.CookieRefresh != time.Duration(0)) {
157158
valid_cookie_secret_size := false
158159
for _, i := range []int{16, 24, 32} {
159-
if len(o.CookieSecret) == i {
160+
if len(secretBytes(o.CookieSecret)) == i {
160161
valid_cookie_secret_size = true
161162
}
162163
}
164+
var decoded bool
165+
if string(secretBytes(o.CookieSecret)) != o.CookieSecret {
166+
decoded = true
167+
}
163168
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+
}
164173
msgs = append(msgs, fmt.Sprintf(
165174
"cookie_secret must be 16, 24, or 32 bytes "+
166175
"to create an AES cipher when "+
167176
"pass_access_token == true or "+
168-
"cookie_refresh != 0, but is %d bytes",
169-
len(o.CookieSecret)))
177+
"cookie_refresh != 0, but is %d bytes.%s",
178+
len(secretBytes(o.CookieSecret)), suffix))
170179
}
171180
}
172181

@@ -251,3 +260,26 @@ func parseSignatureKey(o *Options, msgs []string) []string {
251260
}
252261
return msgs
253262
}
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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,39 @@ func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
160160
o := testOptions()
161161
assert.Equal(t, nil, o.Validate())
162162

163-
o.CookieSecret = "0123456789abcdef"
163+
o.CookieSecret = "0123456789abcdefabcd"
164164
o.CookieRefresh = o.CookieExpire
165165
assert.NotEqual(t, nil, o.Validate())
166166

167167
o.CookieRefresh -= time.Duration(1)
168168
assert.Equal(t, nil, o.Validate())
169169
}
170170

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+
171196
func TestValidateSignatureKey(t *testing.T) {
172197
o := testOptions()
173198
o.SignatureKey = "sha1:secret"

providers/github.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package providers
33
import (
44
"encoding/json"
55
"fmt"
6-
"strings"
76
"io/ioutil"
87
"log"
98
"net/http"
109
"net/url"
10+
"strings"
1111
)
1212

1313
type GitHubProvider struct {
@@ -64,7 +64,7 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
6464
"limit": {"100"},
6565
}
6666

67-
endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/orgs?" + params.Encode()
67+
endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/orgs?" + params.Encode()
6868
req, _ := http.NewRequest("GET", endpoint, nil)
6969
req.Header.Set("Accept", "application/vnd.github.v3+json")
7070
resp, err := http.DefaultClient.Do(req)

providers/session_state_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ const secret = "0123456789abcdefghijklmnopqrstuv"
1313
const altSecret = "0000000000abcdefghijklmnopqrstuv"
1414

1515
func TestSessionStateSerialization(t *testing.T) {
16-
c, err := cookie.NewCipher(secret)
16+
c, err := cookie.NewCipher([]byte(secret))
1717
assert.Equal(t, nil, err)
18-
c2, err := cookie.NewCipher(altSecret)
18+
c2, err := cookie.NewCipher([]byte(altSecret))
1919
assert.Equal(t, nil, err)
2020
s := &SessionState{
2121

0 commit comments

Comments
 (0)