Skip to content

Commit 475ceab

Browse files
committed
demo_client: Pass discoverable credential preference from dropdown
1 parent 1f9edba commit 475ceab

File tree

4 files changed

+297
-178
lines changed

4 files changed

+297
-178
lines changed

demo_client/gui.py

Lines changed: 83 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import webauthn # noqa: E402
2525
import util # noqa: E402
2626

27+
2728
def dbus_error_from_message(msg: Message):
2829
assert msg.message_type == MessageType.ERROR
2930
return DBusError(msg.error_name, msg.body[0] if msg.body else None, reply=msg)
@@ -48,8 +49,8 @@ class MainWindow(Gtk.ApplicationWindow):
4849
username = Gtk.Template.Child()
4950
make_credential_btn = Gtk.Template.Child()
5051
get_assertion_btn = Gtk.Template.Child()
51-
resident_credential_options_list = ["preferred", "required", "discouraged"]
52-
uv_prefs_dropdown = Gtk.Template.Child()
52+
uv_pref_dropdown = Gtk.Template.Child()
53+
discoverable_cred_pref_dropdown = Gtk.Template.Child()
5354
rp_id = "example.com"
5455
origin = "https://example.com"
5556
interface = None
@@ -58,7 +59,6 @@ def on_activate(self, app):
5859
# Create a Builder
5960
builder = Gtk.Builder()
6061
builder.add_from_file("build/window.ui")
61-
self.uv_prefs_list = Gtk.StringList()
6262
# Obtain and show the main window
6363
self.win = builder.get_object("main_window")
6464
self.win.set_application(
@@ -72,20 +72,27 @@ def on_register(self, *args):
7272
now = math.floor(time.time())
7373
cur = DB.cursor()
7474
username = self.username.get_text()
75-
cur.execute("select user_id, user_handle from users where username = ?", (username,))
75+
cur.execute(
76+
"select user_id, user_handle from users where username = ?", (username,)
77+
)
7678
if row := cur.fetchone():
7779
user_id = row[0]
7880
user_handle = row[1]
7981
print(f"user found for {username}: <id: {user_id}, handle: {user_handle}>")
8082
else:
8183
user_handle = secrets.token_bytes(16)
8284
user_id = None
83-
print(f"user created for {username}: <id: {user_id}, handle: {user_handle}>")
85+
print(
86+
f"user created for {username}: <id: {user_id}, handle: {user_handle}>"
87+
)
8488
options = self._get_registration_options(user_handle, username)
8589
print(f"registration options: {options}")
8690
auth_data = create_passkey(INTERFACE, self.origin, self.origin, options)
8791
if not user_id:
88-
cur.execute("insert into users (username, user_handle, created_time) values (?, ?, ?)", (username, user_handle, now))
92+
cur.execute(
93+
"insert into users (username, user_handle, created_time) values (?, ?, ?)",
94+
(username, user_handle, now),
95+
)
8996
user_id = cur.lastrowid
9097
params = {
9198
"user_handle": user_handle,
@@ -125,74 +132,108 @@ def on_authenticate(self, *args):
125132
cur.execute(sql, (username,))
126133
user_creds = []
127134
for row in cur.fetchall():
128-
[user_handle, cred_id, backup_eligible, backup_state, pub_key, sign_count] = row
135+
[
136+
user_handle,
137+
cred_id,
138+
backup_eligible,
139+
backup_state,
140+
pub_key,
141+
sign_count,
142+
] = row
129143
user_cred = {
130-
'user_handle': user_handle,
131-
'cred_id': cred_id,
132-
'backup_eligible': backup_eligible,
133-
'backup_state': backup_state,
134-
'pub_key': pub_key,
135-
'sign_count': sign_count,
144+
"user_handle": user_handle,
145+
"cred_id": cred_id,
146+
"backup_eligible": backup_eligible,
147+
"backup_state": backup_state,
148+
"pub_key": pub_key,
149+
"sign_count": sign_count,
136150
}
137151
user_creds.append(user_cred)
138-
print(user_creds)
139-
cred_ids = [c['cred_id'] for c in user_creds]
152+
cred_ids = [c["cred_id"] for c in user_creds]
140153
else:
141154
print("using username-less flow")
142155
cred_ids = []
143156

144157
options = self._get_authentication_options(cred_ids)
145158
print(f"authenticate clicked: {options}")
146-
def retrieve_user_cred(user_handle: Optional[bytes], cred_id: bytes) -> Optional[dict]:
147-
print(user_handle, cred_id)
159+
160+
def retrieve_user_cred(
161+
user_handle: Optional[bytes], cred_id: bytes
162+
) -> Optional[dict]:
148163
with closing(DB.cursor()) as cur:
149164
if username:
150165
print("using cached user creds")
151-
return next((u for u in user_creds if u['cred_id'] == cred_id and (user_handle is None or user_handle == u['user_handle'])), None)
166+
return next(
167+
(
168+
u
169+
for u in user_creds
170+
if u["cred_id"] == cred_id
171+
and (user_handle is None or user_handle == u["user_handle"])
172+
),
173+
None,
174+
)
152175
else:
153176
if not user_handle:
154177
print("No user handle given, cannot look up user")
155178
return None
156179
sql = """
157-
select user_handle, cred_id, backup_eligible, backup_state, pub_key, sign_count
180+
select user_handle, cred_id, backup_eligible, backup_state, cose_pub_key, sign_count
158181
from user_passkeys
159182
where user_handle = ? and cred_id = ?
160183
"""
161184
cur.execute(sql, (user_handle, cred_id))
162185
if row := cur.fetchone():
163-
[user_handle, cred_id, backup_eligible, backup_state, pub_key, sign_count] = row
186+
[
187+
user_handle,
188+
cred_id,
189+
backup_eligible,
190+
backup_state,
191+
pub_key,
192+
sign_count,
193+
] = row
164194
user_cred = {
165-
'user_handle': user_handle,
166-
'cred_id': cred_id,
167-
'backup_eligible': backup_eligible,
168-
'backup_state': backup_state,
169-
'pub_key': pub_key,
170-
'sign_count': sign_count,
195+
"user_handle": user_handle,
196+
"cred_id": cred_id,
197+
"backup_eligible": backup_eligible,
198+
"backup_state": backup_state,
199+
"pub_key": pub_key,
200+
"sign_count": sign_count,
171201
}
172202
return user_cred
173203
else:
174204
return None
175205

176-
auth_data = get_passkey(INTERFACE, self.origin, self.origin, self.rp_id, cred_ids, retrieve_user_cred)
177-
print("Received passkey", auth_data)
206+
auth_data = get_passkey(
207+
INTERFACE,
208+
self.origin,
209+
self.origin,
210+
self.rp_id,
211+
cred_ids,
212+
retrieve_user_cred,
213+
)
214+
print("Received passkey:")
215+
pprint(auth_data)
178216

179217
@GObject.Property(type=Gtk.StringList)
180-
def uv_prefs(self):
218+
def uv_pref(self):
181219
model = Gtk.StringList()
182220
for o in ["preferred", "required", "discouraged"]:
183221
model.append(o)
184222
return model
185223

186224
@GObject.Property(type=Gtk.StringList)
187-
def resident_credential_options(self):
225+
def discoverable_cred_pref(self):
188226
model = Gtk.StringList()
189227
for o in ["preferred", "required", "discouraged"]:
190228
model.append(o)
191229
return model
192230

193231
def _get_registration_options(self, user_handle: bytes, username: str):
194232
username = self.username.get_text()
195-
user_verification = self.uv_prefs_dropdown.get_selected_item().get_string()
233+
user_verification = self.uv_pref_dropdown.get_selected_item().get_string()
234+
resident_key = (
235+
self.discoverable_cred_pref_dropdown.get_selected_item().get_string()
236+
)
196237
options = {
197238
"challenge": util.b64_encode(secrets.token_bytes(16)),
198239
"rp": {
@@ -210,6 +251,9 @@ def _get_registration_options(self, user_handle: bytes, username: str):
210251
{"type": "public-key", "alg": -8},
211252
],
212253
"userVerification": user_verification,
254+
"authenticatorSelection": {
255+
"residentKey": resident_key,
256+
},
213257
}
214258

215259
return options
@@ -243,7 +287,7 @@ def create_passkey(
243287
print(
244288
f"Sending {'same' if is_same_origin else 'cross'}-origin request for {origin} using options:"
245289
)
246-
pprint(options)
290+
# pprint(options)
247291
print()
248292

249293
req_json = json.dumps(options)
@@ -268,9 +312,8 @@ def create_passkey(
268312
)
269313
return webauthn.verify_create_response(response_json, options, origin)
270314

271-
def get_passkey(
272-
interface, origin, top_origin, rp_id, cred_ids, cred_lookup_fn
273-
):
315+
316+
def get_passkey(interface, origin, top_origin, rp_id, cred_ids, cred_lookup_fn):
274317
is_same_origin = origin == top_origin
275318
options = {
276319
"challenge": util.b64_encode(secrets.token_bytes(16)),
@@ -283,7 +326,7 @@ def get_passkey(
283326
print(
284327
f"Sending {'same' if is_same_origin else 'cross'}-origin request for {origin} using options:"
285328
)
286-
pprint(options)
329+
# pprint(options)
287330
print()
288331

289332
req_json = json.dumps(options)
@@ -305,10 +348,13 @@ def get_passkey(
305348
response_json = json.loads(
306349
rsp["public_key"].value["authentication_response_json"].value
307350
)
308-
response_json['rawId'] = util.b64_decode(response_json['rawId'])
351+
response_json["rawId"] = util.b64_decode(response_json["rawId"])
352+
if user_handle := response_json["response"].get("userHandle"):
353+
response_json["response"]["userHandle"] = util.b64_decode(user_handle)
309354

310355
return webauthn.verify_get_response(response_json, options, origin, cred_lookup_fn)
311356

357+
312358
def connect_to_bus():
313359
global INTERFACE
314360
bus = MessageBus().connect_sync()
@@ -335,7 +381,6 @@ def setup_db():
335381
/ "xyz.iinuwa.credentialsd.DemoCredentialsUi"
336382
/ "users.db"
337383
)
338-
print(db_path)
339384
db_path.parent.mkdir(exist_ok=True)
340385

341386
DB = sqlite3.connect(db_path)

demo_client/main.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ async def run(cmd):
7575
elif cmd == "get":
7676
user_data = json.load(open("./user.json", "r"))
7777
cred_id = util.b64_decode(user_data["cred_id"])
78-
user_data['cred_id'] = cred_id
79-
user_data['pub_key'] = util.b64_decode(user_data['pub_key'])
78+
user_data["cred_id"] = cred_id
79+
user_data["pub_key"] = util.b64_decode(user_data["pub_key"])
8080
try:
8181
auth_data = await get_passkey(
8282
interface, origin, top_origin, rp_id, cred_id, user_data
@@ -226,9 +226,13 @@ async def get_passkey(
226226
response_json = json.loads(
227227
rsp["public_key"].value["authentication_response_json"].value
228228
)
229-
response_json['rawId'] = util.b64_decode(response_json['rawId'])
229+
response_json["rawId"] = util.b64_decode(response_json["rawId"])
230+
if user_handle_b64 := response_json["response"]["userHandle"]:
231+
response_json["response"]["userHandle"] = util.b64_decode(user_handle_b64)
232+
230233
def lookup_fn(user_handle, cred_id):
231234
return user
235+
232236
return webauthn.verify_get_response(response_json, options, origin, lookup_fn)
233237

234238

0 commit comments

Comments
 (0)