cedarcode / webauthn-ruby

WebAuthn ruby server library ― Make your Ruby/Rails web server become a conformant WebAuthn Relying Party
https://rubygems.org/gems/webauthn
MIT License
644 stars 51 forks source link

RelyingParty should support a list of origins #428

Open asavageiv opened 3 months ago

asavageiv commented 3 months ago

While implementing Webauthn for Web + Mobile I found that Android requires using the APK hash as the origin. It is valid to have a list of valid origins per 13.4.9 Validating the origin of a credential.

This means that if you want to implement both Web and Mobile authentication you currently need to create multiple RelyingParty instances, but I think per the spec it would make more sense for RelyingParty to replace origin with accepted_origins that is an array of origins that can be validated against.

The rename helps clarify that the origins listed are not a property of the RelyingParty itself, but of the client per the definitiion.

Does this make sense?

santiagorodriguez96 commented 3 months ago

Hi again @asavageiv!

Thank you the report!

It is true that we don't currently allow the relying parties to specify a list of allowed origins – now that it made it to the level 3 draft, we will have to look into it 👀

asavageiv commented 3 months ago

If anyone is implementing level 3 and needs a quick workaround below is what I did. LMK if you're interested in a PR to for the library properly.

# Work around for the WebAuthn::RelyingParty class not accepting multiple
# origins.
# https://github.com/cedarcode/webauthn-ruby/issues/428
class MyRelyingParty < WebAuthn::RelyingParty
  def initialize(**kwargs)
    @accepted_origins = kwargs.delete(:accepted_origins)
    super
  end

  def verify_registration(raw_credential, challenge, user_verification: nil)
    @accepted_origins.each_with_index do |origin, i|
      @origin = origin
      return super(raw_credential, challenge, user_verification:)
    rescue ::WebAuthn::OriginVerificationError => e
      raise e if i == @accepted_origins.length - 1
    end
  end

  def verify_authentication(
    raw_credential,
    challenge,
    user_verification: nil,
    public_key: nil,
    sign_count: nil
  )
    @accepted_origins.each_with_index do |origin, i|
      @origin = origin
      return(
        super(
          raw_credential,
          challenge,
          user_verification:,
          public_key:,
          sign_count:,
        )
      )
    rescue ::WebAuthn::OriginVerificationError => e
      raise e if i == @accepted_origins.length - 1
    end
  end
end