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

Commit

Permalink
Allow for other signature algorithms than sha1
Browse files Browse the repository at this point in the history
  • Loading branch information
Mike Bland committed Oct 1, 2015
1 parent 64f2f2b commit 16832c7
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 45 deletions.
21 changes: 11 additions & 10 deletions oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,17 @@ type OauthProxy struct {
}

type UpstreamProxy struct {
upstream string
handler http.Handler
signatureKey string
upstream string
handler http.Handler
signature *SignatureData
}

func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("GAP-Upstream-Address", u.upstream)
if u.signatureKey != "" {
if u.signature != nil {
r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth"))
sig := signature.RequestSignature(r, u.signatureKey)
sig := signature.RequestSignature(r, u.signature.hash,
u.signature.key)
r.Header.Set("GAP-Signature", sig)
}
u.handler.ServeHTTP(w, r)
Expand Down Expand Up @@ -107,19 +108,19 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
} else {
setProxyDirector(proxy)
}
signatureKey := opts.upstreamKeys[u.Host]
if signatureKey == "" {
signatureKey = opts.SignatureKey
signatureData := opts.upstreamKeys[u.Host]
if signatureData == nil {
signatureData = opts.signatureData
}
serveMux.Handle(path,
&UpstreamProxy{u.Host, proxy, signatureKey})
&UpstreamProxy{u.Host, proxy, signatureData})
case "file":
if u.Fragment != "" {
path = u.Fragment
}
log.Printf("mapping path %q => file system %q", path, u.Path)
proxy := NewFileServer(path, u.Path)
serveMux.Handle(path, &UpstreamProxy{path, proxy, ""})
serveMux.Handle(path, &UpstreamProxy{path, proxy, nil})
default:
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
}
Expand Down
8 changes: 4 additions & 4 deletions oauthproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ func TestNoRequestSignature(t *testing.T) {
func TestDefaultRequestSignature(t *testing.T) {
st := NewSignatureTest()
defer st.Close()
st.opts.SignatureKey = "foobar"
st.opts.SignatureKey = "sha1:foobar"
st.MakeRequestWithExpectedKey("GET", "", "foobar")
assert.Equal(t, 200, st.rw.Code)
assert.Equal(t, st.rw.Body.String(), "signatures match")
Expand All @@ -653,7 +653,7 @@ func TestDefaultRequestSignature(t *testing.T) {
func TestDefaultRequestSignaturePostRequest(t *testing.T) {
st := NewSignatureTest()
defer st.Close()
st.opts.SignatureKey = "foobar"
st.opts.SignatureKey = "sha1:foobar"
payload := `{ "hello": "world!" }`
st.MakeRequestWithExpectedKey("POST", payload, "foobar")
assert.Equal(t, 200, st.rw.Code)
Expand All @@ -663,9 +663,9 @@ func TestDefaultRequestSignaturePostRequest(t *testing.T) {
func TestUpstreamSpecificRequestSignature(t *testing.T) {
st := NewSignatureTest()
defer st.Close()
st.opts.SignatureKey = "foobar"
st.opts.SignatureKey = "sha1:foobar"
st.opts.UpstreamKeys = append(st.opts.UpstreamKeys,
st.upstream_host+"=bazquux")
st.upstream_host+"=sha1:bazquux")
st.MakeRequestWithExpectedKey("GET", "", "bazquux")
assert.Equal(t, 200, st.rw.Code)
assert.Equal(t, st.rw.Body.String(), "signatures match")
Expand Down
61 changes: 55 additions & 6 deletions options.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"crypto"
"fmt"
"net/url"
"os"
Expand All @@ -9,6 +10,7 @@ import (
"time"

"github.com/bitly/oauth2_proxy/providers"
"github.com/bitly/oauth2_proxy/signature"
)

// Configuration Options that can be set by Command Line Flag, or Config File
Expand Down Expand Up @@ -68,7 +70,13 @@ type Options struct {
proxyUrls []*url.URL
CompiledRegex []*regexp.Regexp
provider providers.Provider
upstreamKeys map[string]string
signatureData *SignatureData
upstreamKeys map[string]*SignatureData
}

type SignatureData struct {
hash crypto.Hash
key string
}

func NewOptions() *Options {
Expand All @@ -87,7 +95,7 @@ func NewOptions() *Options {
PassHostHeader: true,
ApprovalPrompt: "force",
RequestLogging: true,
upstreamKeys: make(map[string]string),
upstreamKeys: make(map[string]*SignatureData),
}
}

Expand Down Expand Up @@ -219,6 +227,12 @@ func parseProviderInfo(o *Options, msgs []string) []string {
}

func parseSignatureKeys(o *Options, msgs []string) []string {
var specErr string
o.signatureData, specErr = parseSignatureSpec(o.SignatureKey)
if specErr != "" {
msgs = append(msgs, specErr+": "+o.SignatureKey)
}

numKeys := len(o.UpstreamKeys)
if numKeys == 0 {
return msgs
Expand All @@ -231,16 +245,28 @@ func parseSignatureKeys(o *Options, msgs []string) []string {

invalidSpecs := make([]string, 0)
invalidHosts := make([]string, 0)
o.upstreamKeys = make(map[string]string, numKeys)
duplicateHosts := make([]string, 0)

for i := 0; i != numKeys; i++ {
keySpec := o.UpstreamKeys[i]
if hostKey := strings.Split(keySpec, "="); len(hostKey) != 2 {
hostKey := strings.Split(keySpec, "=")
if len(hostKey) != 2 {
invalidSpecs = append(invalidSpecs, keySpec)
} else if hostSet[hostKey[0]] == false {
continue
}

host, spec := hostKey[0], hostKey[1]
if hostSet[host] == false {
invalidHosts = append(invalidHosts, keySpec)
} else if o.upstreamKeys[host] != nil {
duplicateHosts = append(duplicateHosts, keySpec)
}

if sigData, specErr := parseSignatureSpec(spec); specErr != "" {
invalidSpecs = append(invalidSpecs, specErr+": "+
keySpec)
} else {
o.upstreamKeys[hostKey[0]] = hostKey[1]
o.upstreamKeys[host] = sigData
}
}

Expand All @@ -253,5 +279,28 @@ func parseSignatureKeys(o *Options, msgs []string) []string {
"any defined upstreams:\n "+
strings.Join(invalidHosts, "\n "))
}
if len(duplicateHosts) != 0 {
msgs = append(msgs,
"specs that duplicate other host specs:\n "+
strings.Join(duplicateHosts, "\n "))
}
return msgs
}

func parseSignatureSpec(data string) (result *SignatureData, err string) {
if data == "" {
return nil, ""
}

components := strings.Split(data, ":")
if len(components) != 2 {
return nil, "invalid signature hash:key spec"
}

algorithm, secretKey := components[0], components[1]
if hash, err := signature.HashAlgorithm(algorithm); err != nil {
return nil, "unsupported signature hash algorithm"
} else {
return &SignatureData{hash, secretKey}, ""
}
}
44 changes: 32 additions & 12 deletions options_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"crypto"
"net/url"
"strings"
"testing"
Expand Down Expand Up @@ -176,15 +177,18 @@ func TestValidateUpstreamSignatureKeys(t *testing.T) {
"https://bar.com/bar",
"https://baz.com",
}
o.SignatureKey = "default secret"
upstreamKeys := "foo.com:8000=secret0,bar.com=secret1,baz.com=secret2"
o.UpstreamKeys = strings.Split(upstreamKeys, ",")
o.SignatureKey = "sha1:default secret"
o.UpstreamKeys = []string{
"foo.com:8000=sha1:secret0",
"bar.com=sha1:secret1",
"baz.com=sha1:secret2",
}

assert.Equal(t, nil, o.Validate())
assert.Equal(t, o.upstreamKeys, map[string]string{
"foo.com:8000": "secret0",
"bar.com": "secret1",
"baz.com": "secret2",
assert.Equal(t, o.upstreamKeys, map[string]*SignatureData{
"foo.com:8000": &SignatureData{crypto.SHA1, "secret0"},
"bar.com": &SignatureData{crypto.SHA1, "secret1"},
"baz.com": &SignatureData{crypto.SHA1, "secret2"},
})
}

Expand All @@ -195,17 +199,33 @@ func TestValidateUpstreamSignatureKeysWithErrors(t *testing.T) {
o.Upstreams = []string{
"https://bar.com/bar",
"https://baz.com",
"https://quux.com",
"https://xyzzy.com",
}
o.SignatureKey = "unsupported:default secret"
o.UpstreamKeys = []string{
"foo.com:8000=sha1:secret0",
"bar.com=secret1",
"baz.com:sha1:secret2",
"quux.com=sha1:secret3",
"quux.com=sha1:secret4",
"xyzzy.com=unsupported:secret5",
}
o.SignatureKey = "default secret"
upstreamKeys := "foo.com:8000=secret0,bar.com=secret1,baz.com:secret2"
o.UpstreamKeys = strings.Split(upstreamKeys, ",")

err := o.Validate()
assert.NotEqual(t, nil, err)
expected := errorMsg([]string{
"unsupported signature hash algorithm: " +
"unsupported:default secret",
"invalid upstream key specs:",
" baz.com:secret2",
" invalid signature hash:key spec: bar.com=secret1",
" baz.com:sha1:secret2",
" unsupported signature hash algorithm: " +
"xyzzy.com=unsupported:secret5",
"specs with hosts that do not match any defined upstreams:",
" foo.com:8000=secret0"})
" foo.com:8000=sha1:secret0",
"specs that duplicate other host specs:",
" quux.com=sha1:secret4",
})
assert.Equal(t, err.Error(), expected)
}
45 changes: 38 additions & 7 deletions signature/signature.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
package signature

import (
"crypto"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"net/http"
"strconv"
"strings"
)

var supportedAlgorithms map[string]crypto.Hash
var algorithmName map[crypto.Hash]string

func init() {
supportedAlgorithms = map[string]crypto.Hash{
"sha1": crypto.SHA1,
}

algorithmName = make(map[crypto.Hash]string)
for name, algorithm := range supportedAlgorithms {
algorithmName[algorithm] = name
}
}

// The string to sign is based on the following request elements, inspired by:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
func StringToSign(req *http.Request) string {
Expand All @@ -28,8 +42,24 @@ func StringToSign(req *http.Request) string {
}, "\n")
}

func RequestSignature(req *http.Request, secretKey string) string {
h := hmac.New(sha1.New, []byte(secretKey))
type unsupportedAlgorithm struct {
algorithm string
}

func (e unsupportedAlgorithm) Error() string {
return "unsupported request signature algorithm: " + e.algorithm
}

func HashAlgorithm(algorithm string) (result crypto.Hash, err error) {
if result = supportedAlgorithms[algorithm]; result == crypto.Hash(0) {
err = unsupportedAlgorithm{algorithm}
}
return
}

func RequestSignature(req *http.Request, hashAlgorithm crypto.Hash,
secretKey string) string {
h := hmac.New(hashAlgorithm.New, []byte(secretKey))
h.Write([]byte(StringToSign(req)))

if req.ContentLength != -1 && req.Body != nil {
Expand All @@ -40,7 +70,8 @@ func RequestSignature(req *http.Request, secretKey string) string {

var sig []byte
sig = h.Sum(sig)
return "sha1 " + base64.URLEncoding.EncodeToString(sig)
return algorithmName[hashAlgorithm] + " " +
base64.URLEncoding.EncodeToString(sig)
}

type ValidationResult int
Expand Down Expand Up @@ -71,13 +102,13 @@ func ValidateRequest(request *http.Request, key string) (
return
}

algorithm := components[0]
if algorithm != "sha1" {
algorithm, err := HashAlgorithm(components[0])
if err != nil {
result = UNSUPPORTED_ALGORITHM
return
}

computedSignature = RequestSignature(request, key)
computedSignature = RequestSignature(request, algorithm, key)
if hmac.Equal([]byte(headerSignature), []byte(computedSignature)) {
result = MATCH
} else {
Expand Down
Loading

0 comments on commit 16832c7

Please sign in to comment.