Skip to content

Commit b7e1e05

Browse files
Merge pull request #435 from cedarcode/sr--skip-user-presence-for-assertions
Allow to skip `user_presence` check for assertions
2 parents b12bc58 + f5cd763 commit b7e1e05

6 files changed

Lines changed: 329 additions & 13 deletions

lib/webauthn/authenticator_assertion_response.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,22 @@ def initialize(authenticator_data:, signature:, user_handle: nil, **options)
3737
@user_handle = user_handle
3838
end
3939

40-
def verify(expected_challenge, expected_origin = nil, public_key:, sign_count:, user_verification: nil, rp_id: nil)
41-
super(expected_challenge, expected_origin, user_verification: user_verification, rp_id: rp_id)
40+
def verify(
41+
expected_challenge,
42+
expected_origin = nil,
43+
public_key:,
44+
sign_count:,
45+
user_presence: nil,
46+
user_verification: nil,
47+
rp_id: nil
48+
)
49+
super(
50+
expected_challenge,
51+
expected_origin,
52+
user_presence: user_presence,
53+
user_verification: user_verification,
54+
rp_id: rp_id
55+
)
4256
verify_item(:signature, WebAuthn::PublicKey.deserialize(public_key))
4357
verify_item(:sign_count, sign_count)
4458

lib/webauthn/public_key_credential_with_assertion.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ def self.response_class
99
WebAuthn::AuthenticatorAssertionResponse
1010
end
1111

12-
def verify(challenge, public_key:, sign_count:, user_verification: nil)
12+
def verify(challenge, public_key:, sign_count:, user_presence: nil, user_verification: nil)
1313
super
1414

1515
response.verify(
1616
encoder.decode(challenge),
1717
public_key: encoder.decode(public_key),
1818
sign_count: sign_count,
19+
user_presence: user_presence,
1920
user_verification: user_verification,
2021
rp_id: appid_extension_output ? appid : nil
2122
)

lib/webauthn/relying_party.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def options_for_authentication(**keyword_arguments)
9999
def verify_authentication(
100100
raw_credential,
101101
challenge,
102+
user_presence: nil,
102103
user_verification: nil,
103104
public_key: nil,
104105
sign_count: nil
@@ -111,6 +112,7 @@ def verify_authentication(
111112
challenge,
112113
public_key: public_key || stored_credential.public_key,
113114
sign_count: sign_count || stored_credential.sign_count,
115+
user_presence: user_presence,
114116
user_verification: user_verification
115117
)
116118
block_given? ? [webauthn_credential, stored_credential] : webauthn_credential

spec/webauthn/authenticator_assertion_response_spec.rb

Lines changed: 208 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,19 +103,217 @@
103103
end
104104

105105
describe "user present validation" do
106-
let(:assertion) { client.get(challenge: original_challenge, user_present: false, user_verified: false) }
106+
context "when user presence flag is off" do
107+
let(:assertion) { client.get(challenge: original_challenge, user_present: false, user_verified: false) }
108+
109+
context "when silent_authentication is not set" do
110+
context 'when user presence is not set' do
111+
it "doesn't verify" do
112+
expect {
113+
assertion_response.verify(original_challenge, public_key: credential_public_key, sign_count: 0)
114+
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
115+
end
116+
117+
it "is invalid" do
118+
expect(
119+
assertion_response.valid?(original_challenge, public_key: credential_public_key, sign_count: 0)
120+
).to be_falsy
121+
end
122+
end
107123

108-
context "if user flags are off" do
109-
it "doesn't verify" do
110-
expect {
111-
assertion_response.verify(original_challenge, public_key: credential_public_key, sign_count: 0)
112-
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
124+
context 'when user presence is not required' do
125+
it "verifies if user presence is not required" do
126+
expect(
127+
assertion_response.verify(
128+
original_challenge,
129+
public_key: credential_public_key,
130+
sign_count: 0,
131+
user_presence: false
132+
)
133+
).to be_truthy
134+
end
135+
136+
it "is valid" do
137+
expect(
138+
assertion_response.valid?(
139+
original_challenge,
140+
public_key: credential_public_key,
141+
sign_count: 0,
142+
user_presence: false
143+
)
144+
).to be_truthy
145+
end
146+
end
147+
148+
context 'when user presence is required' do
149+
it "doesn't verify" do
150+
expect {
151+
assertion_response.verify(
152+
original_challenge,
153+
public_key: credential_public_key,
154+
sign_count: 0,
155+
user_presence: true
156+
)
157+
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
158+
end
159+
160+
it "is invalid" do
161+
expect(
162+
assertion_response.valid?(
163+
original_challenge,
164+
public_key: credential_public_key,
165+
sign_count: 0,
166+
user_presence: true
167+
)
168+
).to be_falsy
169+
end
170+
end
113171
end
114172

115-
it "is invalid" do
116-
expect(
117-
assertion_response.valid?(original_challenge, public_key: credential_public_key, sign_count: 0)
118-
).to be_falsy
173+
context "when silent_authentication is disabled" do
174+
around do |ex|
175+
old_value = WebAuthn.configuration.silent_authentication
176+
WebAuthn.configuration.silent_authentication = false
177+
178+
ex.run
179+
180+
WebAuthn.configuration.silent_authentication = old_value
181+
end
182+
183+
context 'when user presence is not set' do
184+
it "doesn't verify" do
185+
expect {
186+
assertion_response.verify(original_challenge, public_key: credential_public_key, sign_count: 0)
187+
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
188+
end
189+
190+
it "is invalid" do
191+
expect(
192+
assertion_response.valid?(original_challenge, public_key: credential_public_key, sign_count: 0)
193+
).to be_falsy
194+
end
195+
end
196+
197+
context 'when user presence is not required' do
198+
it "verifies if user presence is not required" do
199+
expect(
200+
assertion_response.verify(
201+
original_challenge,
202+
public_key: credential_public_key,
203+
sign_count: 0,
204+
user_presence: false
205+
)
206+
).to be_truthy
207+
end
208+
209+
it "is valid" do
210+
expect(
211+
assertion_response.valid?(
212+
original_challenge,
213+
public_key: credential_public_key,
214+
sign_count: 0,
215+
user_presence: false
216+
)
217+
).to be_truthy
218+
end
219+
end
220+
221+
context 'when user presence is required' do
222+
it "doesn't verify" do
223+
expect {
224+
assertion_response.verify(
225+
original_challenge,
226+
public_key: credential_public_key,
227+
sign_count: 0,
228+
user_presence: true
229+
)
230+
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
231+
end
232+
233+
it "is invalid" do
234+
expect(
235+
assertion_response.valid?(
236+
original_challenge,
237+
public_key: credential_public_key,
238+
sign_count: 0,
239+
user_presence: true
240+
)
241+
).to be_falsy
242+
end
243+
end
244+
end
245+
246+
context "when silent_authentication is enabled" do
247+
around do |ex|
248+
old_value = WebAuthn.configuration.silent_authentication
249+
WebAuthn.configuration.silent_authentication = true
250+
251+
ex.run
252+
253+
WebAuthn.configuration.silent_authentication = old_value
254+
end
255+
256+
context 'when user presence is not set' do
257+
it "verifies if user presence is not required" do
258+
expect(
259+
assertion_response.verify(original_challenge, public_key: credential_public_key, sign_count: 0)
260+
).to be_truthy
261+
end
262+
263+
it "is valid" do
264+
expect(
265+
assertion_response.valid?(original_challenge, public_key: credential_public_key, sign_count: 0)
266+
).to be_truthy
267+
end
268+
end
269+
270+
context 'when user presence is not required' do
271+
it "verifies if user presence is not required" do
272+
expect(
273+
assertion_response.verify(
274+
original_challenge,
275+
public_key: credential_public_key,
276+
sign_count: 0,
277+
user_presence: false
278+
)
279+
).to be_truthy
280+
end
281+
282+
it "is valid" do
283+
expect(
284+
assertion_response.valid?(
285+
original_challenge,
286+
public_key: credential_public_key,
287+
sign_count: 0,
288+
user_presence: false
289+
)
290+
).to be_truthy
291+
end
292+
end
293+
294+
context 'when user presence is required' do
295+
it "doesn't verify" do
296+
expect {
297+
assertion_response.verify(
298+
original_challenge,
299+
public_key: credential_public_key,
300+
sign_count: 0,
301+
user_presence: true
302+
)
303+
}.to raise_exception(WebAuthn::UserPresenceVerificationError)
304+
end
305+
306+
it "is invalid" do
307+
expect(
308+
assertion_response.valid?(
309+
original_challenge,
310+
public_key: credential_public_key,
311+
sign_count: 0,
312+
user_presence: true
313+
)
314+
).to be_falsy
315+
end
316+
end
119317
end
120318
end
121319
end

spec/webauthn/public_key_credential_with_assertion_spec.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,5 +352,41 @@
352352
end
353353
end
354354
end
355+
356+
context "when user_presence" do
357+
context "is not set" do
358+
it "correcly delegates its value to the response" do
359+
expect(assertion_response).to receive(:verify).with(anything, hash_including(user_presence: nil))
360+
361+
public_key_credential.verify(challenge, public_key: credential_public_key, sign_count: credential_sign_count)
362+
end
363+
end
364+
365+
context "is set to false" do
366+
it "correcly delegates its value to the response" do
367+
expect(assertion_response).to receive(:verify).with(anything, hash_including(user_presence: false))
368+
369+
public_key_credential.verify(
370+
challenge,
371+
public_key: credential_public_key,
372+
sign_count: credential_sign_count,
373+
user_presence: false
374+
)
375+
end
376+
end
377+
378+
context "is set to true" do
379+
it "correcly delegates its value to the response" do
380+
expect(assertion_response).to receive(:verify).with(anything, hash_including(user_presence: true))
381+
382+
public_key_credential.verify(
383+
challenge,
384+
public_key: credential_public_key,
385+
sign_count: credential_sign_count,
386+
user_presence: true
387+
)
388+
end
389+
end
390+
end
355391
end
356392
end

spec/webauthn/relying_party_spec.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,71 @@
6868
end
6969
end
7070

71+
describe '#verify_authentication' do
72+
let(:options) { admin_rp.options_for_authentication(allow: user.credentials.map(&:webauthn_id)) }
73+
let(:raw_credential) { admin_fake_client.get(challenge: options.challenge, rp_id: admin_rp.id, sign_count: 1) }
74+
75+
let(:admin_credential) { create_credential(client: admin_fake_client, relying_party: admin_rp) }
76+
let(:admin_credential_public_key) { admin_credential[1] }
77+
78+
before do
79+
user.credentials << OpenStruct.new(
80+
webauthn_id: admin_credential.first,
81+
public_key: admin_rp.encoder.encode(admin_credential[1]),
82+
sign_count: 0
83+
)
84+
end
85+
86+
context "when user_presence" do
87+
let(:webauthn_credential_mock) { instance_double('WebAuthn::PublicKeyCredentialWithAssertion', verify: true) }
88+
89+
before do
90+
allow(WebAuthn::Credential).to receive(:from_get).and_return(webauthn_credential_mock)
91+
end
92+
93+
context "is not set" do
94+
it "correcly delegates its value to the response" do
95+
expect(webauthn_credential_mock).to receive(:verify).with(anything, hash_including(user_presence: nil))
96+
97+
admin_rp.verify_authentication(
98+
raw_credential,
99+
options.challenge,
100+
public_key: admin_credential_public_key,
101+
sign_count: 0
102+
)
103+
end
104+
end
105+
106+
context "is set to false" do
107+
it "correcly delegates its value to the response" do
108+
expect(webauthn_credential_mock).to receive(:verify).with(anything, hash_including(user_presence: false))
109+
110+
admin_rp.verify_authentication(
111+
raw_credential,
112+
options.challenge,
113+
public_key: admin_credential_public_key,
114+
sign_count: 0,
115+
user_presence: false
116+
)
117+
end
118+
end
119+
120+
context "is set to true" do
121+
it "correcly delegates its value to the response" do
122+
expect(webauthn_credential_mock).to receive(:verify).with(anything, hash_including(user_presence: true))
123+
124+
admin_rp.verify_authentication(
125+
raw_credential,
126+
options.challenge,
127+
public_key: admin_credential_public_key,
128+
sign_count: 0,
129+
user_presence: true
130+
)
131+
end
132+
end
133+
end
134+
end
135+
71136
context "without having any global configuration" do
72137
let(:consumer_rp) do
73138
WebAuthn::RelyingParty.new(

0 commit comments

Comments
 (0)