Skip to content

Commit

Permalink
Merge pull request #435 from cedarcode/sr--skip-user-presence-for-ass…
Browse files Browse the repository at this point in the history
…ertions

Allow to skip `user_presence` check for assertions
  • Loading branch information
santiagorodriguez96 authored Oct 21, 2024
2 parents b12bc58 + f5cd763 commit b7e1e05
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 13 deletions.
18 changes: 16 additions & 2 deletions lib/webauthn/authenticator_assertion_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,22 @@ def initialize(authenticator_data:, signature:, user_handle: nil, **options)
@user_handle = user_handle
end

def verify(expected_challenge, expected_origin = nil, public_key:, sign_count:, user_verification: nil, rp_id: nil)
super(expected_challenge, expected_origin, user_verification: user_verification, rp_id: rp_id)
def verify(
expected_challenge,
expected_origin = nil,
public_key:,
sign_count:,
user_presence: nil,
user_verification: nil,
rp_id: nil
)
super(
expected_challenge,
expected_origin,
user_presence: user_presence,
user_verification: user_verification,
rp_id: rp_id
)
verify_item(:signature, WebAuthn::PublicKey.deserialize(public_key))
verify_item(:sign_count, sign_count)

Expand Down
3 changes: 2 additions & 1 deletion lib/webauthn/public_key_credential_with_assertion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ def self.response_class
WebAuthn::AuthenticatorAssertionResponse
end

def verify(challenge, public_key:, sign_count:, user_verification: nil)
def verify(challenge, public_key:, sign_count:, user_presence: nil, user_verification: nil)
super

response.verify(
encoder.decode(challenge),
public_key: encoder.decode(public_key),
sign_count: sign_count,
user_presence: user_presence,
user_verification: user_verification,
rp_id: appid_extension_output ? appid : nil
)
Expand Down
2 changes: 2 additions & 0 deletions lib/webauthn/relying_party.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def options_for_authentication(**keyword_arguments)
def verify_authentication(
raw_credential,
challenge,
user_presence: nil,
user_verification: nil,
public_key: nil,
sign_count: nil
Expand All @@ -111,6 +112,7 @@ def verify_authentication(
challenge,
public_key: public_key || stored_credential.public_key,
sign_count: sign_count || stored_credential.sign_count,
user_presence: user_presence,
user_verification: user_verification
)
block_given? ? [webauthn_credential, stored_credential] : webauthn_credential
Expand Down
218 changes: 208 additions & 10 deletions spec/webauthn/authenticator_assertion_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,217 @@
end

describe "user present validation" do
let(:assertion) { client.get(challenge: original_challenge, user_present: false, user_verified: false) }
context "when user presence flag is off" do
let(:assertion) { client.get(challenge: original_challenge, user_present: false, user_verified: false) }

context "when silent_authentication is not set" do
context 'when user presence is not set' do
it "doesn't verify" do
expect {
assertion_response.verify(original_challenge, public_key: credential_public_key, sign_count: 0)
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
end

it "is invalid" do
expect(
assertion_response.valid?(original_challenge, public_key: credential_public_key, sign_count: 0)
).to be_falsy
end
end

context "if user flags are off" do
it "doesn't verify" do
expect {
assertion_response.verify(original_challenge, public_key: credential_public_key, sign_count: 0)
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
context 'when user presence is not required' do
it "verifies if user presence is not required" do
expect(
assertion_response.verify(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: false
)
).to be_truthy
end

it "is valid" do
expect(
assertion_response.valid?(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: false
)
).to be_truthy
end
end

context 'when user presence is required' do
it "doesn't verify" do
expect {
assertion_response.verify(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: true
)
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
end

it "is invalid" do
expect(
assertion_response.valid?(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: true
)
).to be_falsy
end
end
end

it "is invalid" do
expect(
assertion_response.valid?(original_challenge, public_key: credential_public_key, sign_count: 0)
).to be_falsy
context "when silent_authentication is disabled" do
around do |ex|
old_value = WebAuthn.configuration.silent_authentication
WebAuthn.configuration.silent_authentication = false

ex.run

WebAuthn.configuration.silent_authentication = old_value
end

context 'when user presence is not set' do
it "doesn't verify" do
expect {
assertion_response.verify(original_challenge, public_key: credential_public_key, sign_count: 0)
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
end

it "is invalid" do
expect(
assertion_response.valid?(original_challenge, public_key: credential_public_key, sign_count: 0)
).to be_falsy
end
end

context 'when user presence is not required' do
it "verifies if user presence is not required" do
expect(
assertion_response.verify(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: false
)
).to be_truthy
end

it "is valid" do
expect(
assertion_response.valid?(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: false
)
).to be_truthy
end
end

context 'when user presence is required' do
it "doesn't verify" do
expect {
assertion_response.verify(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: true
)
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
end

it "is invalid" do
expect(
assertion_response.valid?(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: true
)
).to be_falsy
end
end
end

context "when silent_authentication is enabled" do
around do |ex|
old_value = WebAuthn.configuration.silent_authentication
WebAuthn.configuration.silent_authentication = true

ex.run

WebAuthn.configuration.silent_authentication = old_value
end

context 'when user presence is not set' do
it "verifies if user presence is not required" do
expect(
assertion_response.verify(original_challenge, public_key: credential_public_key, sign_count: 0)
).to be_truthy
end

it "is valid" do
expect(
assertion_response.valid?(original_challenge, public_key: credential_public_key, sign_count: 0)
).to be_truthy
end
end

context 'when user presence is not required' do
it "verifies if user presence is not required" do
expect(
assertion_response.verify(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: false
)
).to be_truthy
end

it "is valid" do
expect(
assertion_response.valid?(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: false
)
).to be_truthy
end
end

context 'when user presence is required' do
it "doesn't verify" do
expect {
assertion_response.verify(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: true
)
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
end

it "is invalid" do
expect(
assertion_response.valid?(
original_challenge,
public_key: credential_public_key,
sign_count: 0,
user_presence: true
)
).to be_falsy
end
end
end
end
end
Expand Down
36 changes: 36 additions & 0 deletions spec/webauthn/public_key_credential_with_assertion_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -352,5 +352,41 @@
end
end
end

context "when user_presence" do
context "is not set" do
it "correcly delegates its value to the response" do
expect(assertion_response).to receive(:verify).with(anything, hash_including(user_presence: nil))

public_key_credential.verify(challenge, public_key: credential_public_key, sign_count: credential_sign_count)
end
end

context "is set to false" do
it "correcly delegates its value to the response" do
expect(assertion_response).to receive(:verify).with(anything, hash_including(user_presence: false))

public_key_credential.verify(
challenge,
public_key: credential_public_key,
sign_count: credential_sign_count,
user_presence: false
)
end
end

context "is set to true" do
it "correcly delegates its value to the response" do
expect(assertion_response).to receive(:verify).with(anything, hash_including(user_presence: true))

public_key_credential.verify(
challenge,
public_key: credential_public_key,
sign_count: credential_sign_count,
user_presence: true
)
end
end
end
end
end
65 changes: 65 additions & 0 deletions spec/webauthn/relying_party_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,71 @@
end
end

describe '#verify_authentication' do
let(:options) { admin_rp.options_for_authentication(allow: user.credentials.map(&:webauthn_id)) }
let(:raw_credential) { admin_fake_client.get(challenge: options.challenge, rp_id: admin_rp.id, sign_count: 1) }

let(:admin_credential) { create_credential(client: admin_fake_client, relying_party: admin_rp) }
let(:admin_credential_public_key) { admin_credential[1] }

before do
user.credentials << OpenStruct.new(
webauthn_id: admin_credential.first,
public_key: admin_rp.encoder.encode(admin_credential[1]),
sign_count: 0
)
end

context "when user_presence" do
let(:webauthn_credential_mock) { instance_double('WebAuthn::PublicKeyCredentialWithAssertion', verify: true) }

before do
allow(WebAuthn::Credential).to receive(:from_get).and_return(webauthn_credential_mock)
end

context "is not set" do
it "correcly delegates its value to the response" do
expect(webauthn_credential_mock).to receive(:verify).with(anything, hash_including(user_presence: nil))

admin_rp.verify_authentication(
raw_credential,
options.challenge,
public_key: admin_credential_public_key,
sign_count: 0
)
end
end

context "is set to false" do
it "correcly delegates its value to the response" do
expect(webauthn_credential_mock).to receive(:verify).with(anything, hash_including(user_presence: false))

admin_rp.verify_authentication(
raw_credential,
options.challenge,
public_key: admin_credential_public_key,
sign_count: 0,
user_presence: false
)
end
end

context "is set to true" do
it "correcly delegates its value to the response" do
expect(webauthn_credential_mock).to receive(:verify).with(anything, hash_including(user_presence: true))

admin_rp.verify_authentication(
raw_credential,
options.challenge,
public_key: admin_credential_public_key,
sign_count: 0,
user_presence: true
)
end
end
end
end

context "without having any global configuration" do
let(:consumer_rp) do
WebAuthn::RelyingParty.new(
Expand Down

0 comments on commit b7e1e05

Please sign in to comment.