Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore PKCE params for non-PKCE grants #1415

Merged
merged 1 commit into from
May 26, 2020
Merged

Ignore PKCE params for non-PKCE grants #1415

merged 1 commit into from
May 26, 2020

Conversation

toupeira
Copy link
Member

Summary

This aligns the behavior with sections 3.1 and 3.2 from RFC 6749:

The authorization server MUST ignore unrecognized request parameters.

https://tools.ietf.org/html/rfc6749#section-3.1
https://tools.ietf.org/html/rfc6749#section-3.2

Other Information

Closes #1411

@@ -93,7 +84,7 @@ def validate_redirect_uri
# if either side (server or client) request PKCE, check the verifier
# against the DB - if PKCE is supported
def validate_code_verifier
return true unless grant.uses_pkce? || code_verifier
return true unless grant.uses_pkce?
Copy link
Member Author

Choose a reason for hiding this comment

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

This check had to be removed as well, and caused the changed spec in spec/requests/flows/authorization_code_spec.rb to fail.

Copy link
Contributor

@linhdangduy linhdangduy May 22, 2020

Choose a reason for hiding this comment

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

I think we can thinking about validate_code_verifier as following:

  • (1) pkce non supported (pkce_supported? == false) -> return true

  • pkce is supported (pkce_supported? == true)

code_challenge.present? code_challenge.blank?
code_verifier.present? (2) run validate (3) run validate
code_verifier.blank? (5) (already validated at validate_params)return false (4) return true

So, as the table, currently changes (Line 87 and 88) will not run validated on (2), when code_verifier is present, and code_challenge is not sent from client at authorization request, this can causing Downgrade Attack on PKCE

About (5)th condition, it is currently validated at validate_params, but I still check it in the code 👇, because I think we should consider this is independent from validate_params, any change to validate_params should not break validate_code_verifier (because validate_params's task is to informs that client missing necessary parameter)

So I think this validation could be written as below:

def validate_code_verifier
  return true unless pkce_supported? # for (1)

  if code_verifier.blank?
    return true if grant.code_challenge.blank? # for (4)
    return false # for (5)
  end

  # run validate for (2) (3)
  if grant.code_challenge_method == "S256"
  .....
  end
end

Copy link
Member Author

Choose a reason for hiding this comment

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

@linhdangduy thanks, this makes sense to me. 👍

I updated the method now as per your suggestion, though I simplified the 4/5 case into return grant.code_challenge.blank? if code_verifier.blank? and added a spec for the case where PKCE is enabled but both code_challenge and code_verifier are blank. I think the remaining cases should already be covered by the specs in spec/lib/oauth/authorization_code_request_spec.rb.

"error" => "invalid_grant",
"error_description" => translated_error_message(:invalid_grant),
)
url_should_have_param("code", Doorkeeper::AccessGrant.first.token)
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm a bit confused by the PKCE specs in this file because it seems we don't mock Doorkeeper::AccessGrant).pkce_supported?, or set it up through other ways.

Should we add this?

Copy link
Member

Choose a reason for hiding this comment

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

I think yes

Copy link
Member Author

Choose a reason for hiding this comment

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

@nbulaj I saw now that we indirectly set this up by adding the relevant columns: https://github.com/toupeira/doorkeeper/blob/4a7a106dc03411d9ada0a627f6b46e9e8022c393/spec/dummy/db/schema.rb#L25-L28

Running these specs with WITHOUT_PKCE=1 makes them fail, so I think we're good here and I retract my concern 😁

@@ -55,14 +54,6 @@ def pkce_supported?
Doorkeeper.config.access_grant_model.pkce_supported?
end

def validate_pkce_support
@invalid_request_reason = :not_support_pkce if grant &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Because not_support_pkce is used only here, so should remove from other files, too. (en.yml, invalid_request_response.rb)

Copy link
Member Author

Choose a reason for hiding this comment

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

@linhdangduy thanks, missed those and removed them now 👍

@@ -93,7 +84,7 @@ def validate_redirect_uri
# if either side (server or client) request PKCE, check the verifier
# against the DB - if PKCE is supported
def validate_code_verifier
return true unless grant.uses_pkce? || code_verifier
return true unless grant.uses_pkce?
Copy link
Contributor

@linhdangduy linhdangduy May 22, 2020

Choose a reason for hiding this comment

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

I think we can thinking about validate_code_verifier as following:

  • (1) pkce non supported (pkce_supported? == false) -> return true

  • pkce is supported (pkce_supported? == true)

code_challenge.present? code_challenge.blank?
code_verifier.present? (2) run validate (3) run validate
code_verifier.blank? (5) (already validated at validate_params)return false (4) return true

So, as the table, currently changes (Line 87 and 88) will not run validated on (2), when code_verifier is present, and code_challenge is not sent from client at authorization request, this can causing Downgrade Attack on PKCE

About (5)th condition, it is currently validated at validate_params, but I still check it in the code 👇, because I think we should consider this is independent from validate_params, any change to validate_params should not break validate_code_verifier (because validate_params's task is to informs that client missing necessary parameter)

So I think this validation could be written as below:

def validate_code_verifier
  return true unless pkce_supported? # for (1)

  if code_verifier.blank?
    return true if grant.code_challenge.blank? # for (4)
    return false # for (5)
  end

  # run validate for (2) (3)
  if grant.code_challenge_method == "S256"
  .....
  end
end

This aligns with sections 3.1 and 3.2 from RFC 6749:

> The authorization server MUST ignore unrecognized request parameters.

https://tools.ietf.org/html/rfc6749#section-3.1
https://tools.ietf.org/html/rfc6749#section-3.2
Copy link
Member Author

@toupeira toupeira left a comment

Choose a reason for hiding this comment

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

@linhdangduy @nbulaj thanks for your feedback! 🙏 Your comments should be addressed now.

"error" => "invalid_grant",
"error_description" => translated_error_message(:invalid_grant),
)
url_should_have_param("code", Doorkeeper::AccessGrant.first.token)
Copy link
Member Author

Choose a reason for hiding this comment

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

@nbulaj I saw now that we indirectly set this up by adding the relevant columns: https://github.com/toupeira/doorkeeper/blob/4a7a106dc03411d9ada0a627f6b46e9e8022c393/spec/dummy/db/schema.rb#L25-L28

Running these specs with WITHOUT_PKCE=1 makes them fail, so I think we're good here and I retract my concern 😁

@@ -55,14 +54,6 @@ def pkce_supported?
Doorkeeper.config.access_grant_model.pkce_supported?
end

def validate_pkce_support
@invalid_request_reason = :not_support_pkce if grant &&
Copy link
Member Author

Choose a reason for hiding this comment

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

@linhdangduy thanks, missed those and removed them now 👍

@@ -93,7 +84,7 @@ def validate_redirect_uri
# if either side (server or client) request PKCE, check the verifier
# against the DB - if PKCE is supported
def validate_code_verifier
return true unless grant.uses_pkce? || code_verifier
return true unless grant.uses_pkce?
Copy link
Member Author

Choose a reason for hiding this comment

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

@linhdangduy thanks, this makes sense to me. 👍

I updated the method now as per your suggestion, though I simplified the 4/5 case into return grant.code_challenge.blank? if code_verifier.blank? and added a spec for the case where PKCE is enabled but both code_challenge and code_verifier are blank. I think the remaining cases should already be covered by the specs in spec/lib/oauth/authorization_code_request_spec.rb.

Copy link
Member

@nbulaj nbulaj left a comment

Choose a reason for hiding this comment

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

🚝
LGTM

@nbulaj nbulaj merged commit ebe0fc1 into doorkeeper-gem:master May 26, 2020
@nbulaj
Copy link
Member

nbulaj commented May 26, 2020

Thanks @toupeira ! 🤝

@toupeira toupeira deleted the pkce-params-validation branch May 26, 2020 10:08
@nbulaj nbulaj added this to the 5.5 milestone May 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ignore code_verifier for non-PKCE grants
3 participants