Skip to content

Commit

Permalink
Merge pull request from GHSA-vh7g-p26c-j2cw
Browse files Browse the repository at this point in the history
Add HMAC protection on /approval endpoint
  • Loading branch information
sagikazarmark authored Sep 28, 2022
2 parents a483f5b + 793bcc4 commit 49471b1
Show file tree
Hide file tree
Showing 19 changed files with 276 additions and 13 deletions.
32 changes: 31 additions & 1 deletion server/handlers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
Expand Down Expand Up @@ -499,7 +500,15 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth
s.logger.Infof("login successful: connector %q, username=%q, preferred_username=%q, email=%q, groups=%q",
authReq.ConnectorID, claims.Username, claims.PreferredUsername, email, claims.Groups)

returnURL := path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID
// TODO: if s.skipApproval or !authReq.ForceApprovalPrompt, we can skip the redirect to /approval and go ahead and send code

// an HMAC is used here to ensure that the request ID is unpredictable, ensuring that an attacker who intercepted the original
// flow would be unable to poll for the result at the /approval endpoint
h := hmac.New(sha256.New, authReq.HMACKey)
h.Write([]byte(authReq.ID))
mac := h.Sum(nil)

returnURL := path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID + "&hmac=" + base64.RawURLEncoding.EncodeToString(mac)
_, ok := conn.(connector.RefreshConnector)
if !ok {
return returnURL, nil
Expand Down Expand Up @@ -544,6 +553,17 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth
}

func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
macEncoded := r.FormValue("hmac")
if macEncoded == "" {
s.renderError(r, w, http.StatusUnauthorized, "Unauthorized request")
return
}
mac, err := base64.RawURLEncoding.DecodeString(macEncoded)
if err != nil {
s.renderError(r, w, http.StatusUnauthorized, "Unauthorized request")
return
}

authReq, err := s.storage.GetAuthRequest(r.FormValue("req"))
if err != nil {
s.logger.Errorf("Failed to get auth request: %v", err)
Expand All @@ -556,6 +576,16 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
return
}

// build expected hmac with secret key
h := hmac.New(sha256.New, authReq.HMACKey)
h.Write([]byte(authReq.ID))
expectedMAC := h.Sum(nil)
// constant time comparison
if !hmac.Equal(mac, expectedMAC) {
s.renderError(r, w, http.StatusUnauthorized, "Unauthorized request")
return
}

switch r.Method {
case http.MethodGet:
if s.skipApproval {
Expand Down
2 changes: 2 additions & 0 deletions server/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package server

import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
Expand Down Expand Up @@ -576,6 +577,7 @@ func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthReques
CodeChallenge: codeChallenge,
CodeChallengeMethod: codeChallengeMethod,
},
HMACKey: storage.NewHMACKey(crypto.SHA256),
}, nil
}

Expand Down
5 changes: 4 additions & 1 deletion storage/conformance/conformance.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) {
EmailVerified: true,
Groups: []string{"a", "b"},
},
PKCE: codeChallenge,
PKCE: codeChallenge,
HMACKey: []byte("hmac_key"),
}

identity := storage.Claims{Email: "foobar"}
Expand Down Expand Up @@ -137,6 +138,7 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) {
EmailVerified: true,
Groups: []string{"a"},
},
HMACKey: []byte("hmac_key"),
}

if err := s.CreateAuthRequest(a2); err != nil {
Expand Down Expand Up @@ -817,6 +819,7 @@ func testGC(t *testing.T, s storage.Storage) {
EmailVerified: true,
Groups: []string{"a", "b"},
},
HMACKey: []byte("hmac_key"),
}

if err := s.CreateAuthRequest(a); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions storage/ent/client/authrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func (d *Database) CreateAuthRequest(authRequest storage.AuthRequest) error {
SetExpiry(authRequest.Expiry.UTC()).
SetConnectorID(authRequest.ConnectorID).
SetConnectorData(authRequest.ConnectorData).
SetHmacKey(authRequest.HMACKey).
Save(context.TODO())
if err != nil {
return convertDBError("create auth request: %w", err)
Expand Down Expand Up @@ -94,6 +95,7 @@ func (d *Database) UpdateAuthRequest(id string, updater func(old storage.AuthReq
SetExpiry(newAuthRequest.Expiry.UTC()).
SetConnectorID(newAuthRequest.ConnectorID).
SetConnectorData(newAuthRequest.ConnectorData).
SetHmacKey(newAuthRequest.HMACKey).
Save(context.TODO())
if err != nil {
return rollback(tx, "update auth request uploading: %w", err)
Expand Down
1 change: 1 addition & 0 deletions storage/ent/client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func toStorageAuthRequest(a *db.AuthRequest) storage.AuthRequest {
CodeChallenge: a.CodeChallenge,
CodeChallengeMethod: a.CodeChallengeMethod,
},
HMACKey: a.HmacKey,
}
}

Expand Down
12 changes: 11 additions & 1 deletion storage/ent/db/authrequest.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions storage/ent/db/authrequest/authrequest.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 83 additions & 0 deletions storage/ent/db/authrequest/where.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions storage/ent/db/authrequest_create.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions storage/ent/db/authrequest_update.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions storage/ent/db/migrate/schema.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 49471b1

Please sign in to comment.