Skip to content

Commit

Permalink
Add oauth_body_hash to request headers for non-form-encoded bodies
Browse files Browse the repository at this point in the history
 - Proposal: https://tools.ietf.org/id/draft-eaton-oauth-bodyhash-00.html
 - Certain Oauth1 servers have checks for a hashed body payload header
 that gets included in signing of the request. This is important, as it
 helps avoid a MITM attack-vector where an attacker captures a message,
 replaces the body, and forwards the remaining message with already
 signed headers.
 - Per the linked specification: the oauth_body_hash header is NOT
 provided when a x-www-form-urlencoded body is provided, but SHOULD be
 provided for any other body type.
  • Loading branch information
Aric Parkinson committed Jul 25, 2019
1 parent f9f59e0 commit 002fce1
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 8 deletions.
26 changes: 18 additions & 8 deletions auther.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,20 +212,30 @@ func collectParameters(req *http.Request, oauthParams map[string]string) (map[st
// most backends do not accept duplicate query keys
params[key] = value[0]
}
if req.Body != nil && req.Header.Get(contentType) == formContentType {
if req.Body != nil {
// reads data to a []byte, draining req.Body
b, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
values, err := url.ParseQuery(string(b))
if err != nil {
return nil, err
}
for key, value := range values {
// not supporting params with duplicate keys
params[key] = value[0]

if req.Header.Get(contentType) == formContentType {
values, err := url.ParseQuery(string(b))
if err != nil {
return nil, err
}
for key, value := range values {
// not supporting params with duplicate keys
params[key] = value[0]
}
} else {
// providing body hash for requests other than x-www-form-urlencoded
// as described in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-4.1.1
hash := sha1.Sum(b)
hash64 := base64.StdEncoding.EncodeToString(hash[:])
params[oauthBodyHash] = hash64
}

// reinitialize Body with ReadCloser over the []byte
req.Body = ioutil.NopCloser(bytes.NewReader(b))
}
Expand Down
32 changes: 32 additions & 0 deletions auther_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,38 @@ func TestCollectParameters(t *testing.T) {
// http://golang.org/src/net/http/request.go#L837
}

func TestCollectParametersNonWwwFormUrlencoded(t *testing.T) {
oauthParams := map[string]string{
"oauth_token": "kkk9d7dh3k39sjv7",
"oauth_consumer_key": "9djdj82h48djs9d2",
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": "137131201",
"oauth_nonce": "7d8f3e4a",
"realm": "photos",
}
jsonBody := "{ \"test\": \"foobar\" }"
req, err := http.NewRequest("POST", "/request?b5=%3D%253D&a3=a&c%40=&a2=r%20b", strings.NewReader(jsonBody))
assert.Nil(t, err)
req.Header.Set(contentType, "application/json")
params, err := collectParameters(req, oauthParams)
// assert parameters were collected from oauthParams, the query, and form body
// excluding the realm parameter
expected := map[string]string{
"b5": "=%3D",
"a3": "a",
"c@": "",
"a2": "r b",
"oauth_token": "kkk9d7dh3k39sjv7",
"oauth_consumer_key": "9djdj82h48djs9d2",
"oauth_signature_method": "HMAC-SHA1",
"oauth_timestamp": "137131201",
"oauth_nonce": "7d8f3e4a",
"oauth_body_hash": "MFIr5skk2mB20S82rxAYkx9ql/A=",
}
assert.Nil(t, err)
assert.Equal(t, expected, params)
}

func TestSignatureBase(t *testing.T) {
reqA, err := http.NewRequest("get", "HTTPS://HELLO.IO?q=test", nil)
assert.Nil(t, err)
Expand Down

0 comments on commit 002fce1

Please sign in to comment.