Skip to content
This repository has been archived by the owner on Jan 24, 2019. It is now read-only.

Cookie secret encoding? #227

Merged
merged 1 commit into from
Jun 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cookie/cookies.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ type Cipher struct {
}

// NewCipher returns a new aes Cipher for encrypting cookie values
func NewCipher(secret string) (*Cipher, error) {
c, err := aes.NewCipher([]byte(secret))
func NewCipher(secret []byte) (*Cipher, error) {
c, err := aes.NewCipher(secret)
if err != nil {
return nil, err
}
Expand Down
21 changes: 20 additions & 1 deletion cookie/cookies_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cookie

import (
"encoding/base64"
"testing"

"github.com/bmizerany/assert"
Expand All @@ -9,7 +10,25 @@ import (
func TestEncodeAndDecodeAccessToken(t *testing.T) {
const secret = "0123456789abcdefghijklmnopqrstuv"
const token = "my access token"
c, err := NewCipher(secret)
c, err := NewCipher([]byte(secret))
assert.Equal(t, nil, err)

encoded, err := c.Encrypt(token)
assert.Equal(t, nil, err)

decoded, err := c.Decrypt(encoded)
assert.Equal(t, nil, err)

assert.NotEqual(t, token, encoded)
assert.Equal(t, token, decoded)
}

func TestEncodeAndDecodeAccessTokenB64(t *testing.T) {
const secret_b64 = "A3Xbr6fu6Al0HkgrP1ztjb-mYiwmxgNPP-XbNsz1WBk="
const token = "my access token"

secret, err := base64.URLEncoding.DecodeString(secret_b64)
c, err := NewCipher([]byte(secret))
assert.Equal(t, nil, err)

encoded, err := c.Encrypt(token)
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func main() {
flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")

flagSet.String("cookie-name", "_oauth2_proxy", "the name of the cookie that the oauth_proxy creates")
flagSet.String("cookie-secret", "", "the seed string for secure cookies")
flagSet.String("cookie-secret", "", "the seed string for secure cookies (optionally base64 encoded)")
flagSet.String("cookie-domain", "", "an optional cookie domain to force cookies to (ie: .yourcompany.com)*")
flagSet.Duration("cookie-expire", time.Duration(168)*time.Hour, "expire timeframe for cookie")
flagSet.Duration("cookie-refresh", time.Duration(0), "refresh the cookie after this duration; 0 to disable")
Expand Down
9 changes: 4 additions & 5 deletions oauthproxy.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
"encoding/base64"
b64 "encoding/base64"
"errors"
"fmt"
"html/template"
Expand Down Expand Up @@ -164,10 +164,9 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
var cipher *cookie.Cipher
if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
var err error
cipher, err = cookie.NewCipher(opts.CookieSecret)
cipher, err = cookie.NewCipher(secretBytes(opts.CookieSecret))
if err != nil {
log.Fatal("error creating AES cipher with "+
"cookie-secret ", opts.CookieSecret, ": ", err)
log.Fatal("cookie-secret error: ", err)
}
}

Expand Down Expand Up @@ -626,7 +625,7 @@ func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState,
if len(s) != 2 || s[0] != "Basic" {
return nil, fmt.Errorf("invalid Authorization header %s", req.Header.Get("Authorization"))
}
b, err := base64.StdEncoding.DecodeString(s[1])
b, err := b64.StdEncoding.DecodeString(s[1])
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion oauthproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
pc_test.opts = NewOptions()
pc_test.opts.ClientID = "bazquux"
pc_test.opts.ClientSecret = "xyzzyplugh"
pc_test.opts.CookieSecret = "0123456789abcdef"
pc_test.opts.CookieSecret = "0123456789abcdefabcd"
// First, set the CookieRefresh option so proxy.AesCipher is created,
// needed to encrypt the access_token.
pc_test.opts.CookieRefresh = time.Hour
Expand Down
38 changes: 35 additions & 3 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"crypto"
"encoding/base64"
"fmt"
"net/url"
"os"
Expand Down Expand Up @@ -156,17 +157,25 @@ func (o *Options) Validate() error {
if o.PassAccessToken || (o.CookieRefresh != time.Duration(0)) {
valid_cookie_secret_size := false
for _, i := range []int{16, 24, 32} {
if len(o.CookieSecret) == i {
if len(secretBytes(o.CookieSecret)) == i {
valid_cookie_secret_size = true
}
}
var decoded bool
if string(secretBytes(o.CookieSecret)) != o.CookieSecret {
decoded = true
}
if valid_cookie_secret_size == false {
var suffix string
if decoded {
suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.CookieSecret)
}
msgs = append(msgs, fmt.Sprintf(
"cookie_secret must be 16, 24, or 32 bytes "+
"to create an AES cipher when "+
"pass_access_token == true or "+
"cookie_refresh != 0, but is %d bytes",
len(o.CookieSecret)))
"cookie_refresh != 0, but is %d bytes.%s",
len(secretBytes(o.CookieSecret)), suffix))
}
}

Expand Down Expand Up @@ -251,3 +260,26 @@ func parseSignatureKey(o *Options, msgs []string) []string {
}
return msgs
}

func addPadding(secret string) string {
padding := len(secret) % 4
switch padding {
case 1:
return secret + "==="
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case will never happen for a valid base64 encoded string.

case 2:
return secret + "=="
case 3:
return secret + "="
default:
return secret
}
}

// secretBytes attempts to base64 decode the secret, if that fails it treats the secret as binary
func secretBytes(secret string) []byte {
b, err := base64.URLEncoding.DecodeString(addPadding(secret))
if err == nil {
return []byte(addPadding(string(b)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why = pad the binary result of base64 decoding?

}
return []byte(secret)
}
27 changes: 26 additions & 1 deletion options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,39 @@ func TestCookieRefreshMustBeLessThanCookieExpire(t *testing.T) {
o := testOptions()
assert.Equal(t, nil, o.Validate())

o.CookieSecret = "0123456789abcdef"
o.CookieSecret = "0123456789abcdefabcd"
o.CookieRefresh = o.CookieExpire
assert.NotEqual(t, nil, o.Validate())

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

func TestBase64CookieSecret(t *testing.T) {
o := testOptions()
assert.Equal(t, nil, o.Validate())

// 32 byte, base64 (urlsafe) encoded key
o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ="
assert.Equal(t, nil, o.Validate())

// 32 byte, base64 (urlsafe) encoded key, w/o padding
o.CookieSecret = "yHBw2lh2Cvo6aI_jn_qMTr-pRAjtq0nzVgDJNb36jgQ"
assert.Equal(t, nil, o.Validate())

// 24 byte, base64 (urlsafe) encoded key
o.CookieSecret = "Kp33Gj-GQmYtz4zZUyUDdqQKx5_Hgkv3"
assert.Equal(t, nil, o.Validate())

// 16 byte, base64 (urlsafe) encoded key
o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA=="
assert.Equal(t, nil, o.Validate())

// 16 byte, base64 (urlsafe) encoded key, w/o padding
o.CookieSecret = "LFEqZYvYUwKwzn0tEuTpLA"
assert.Equal(t, nil, o.Validate())
}

func TestValidateSignatureKey(t *testing.T) {
o := testOptions()
o.SignatureKey = "sha1:secret"
Expand Down
4 changes: 2 additions & 2 deletions providers/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package providers
import (
"encoding/json"
"fmt"
"strings"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
)

type GitHubProvider struct {
Expand Down Expand Up @@ -64,7 +64,7 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
"limit": {"100"},
}

endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/orgs?" + params.Encode()
endpoint := p.ValidateURL.Scheme + "://" + p.ValidateURL.Host + "/user/orgs?" + params.Encode()
req, _ := http.NewRequest("GET", endpoint, nil)
req.Header.Set("Accept", "application/vnd.github.v3+json")
resp, err := http.DefaultClient.Do(req)
Expand Down
4 changes: 2 additions & 2 deletions providers/session_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ const secret = "0123456789abcdefghijklmnopqrstuv"
const altSecret = "0000000000abcdefghijklmnopqrstuv"

func TestSessionStateSerialization(t *testing.T) {
c, err := cookie.NewCipher(secret)
c, err := cookie.NewCipher([]byte(secret))
assert.Equal(t, nil, err)
c2, err := cookie.NewCipher(altSecret)
c2, err := cookie.NewCipher([]byte(altSecret))
assert.Equal(t, nil, err)
s := &SessionState{
Email: "user@domain.com",
Expand Down