2424import webauthn # noqa: E402
2525import util # noqa: E402
2626
27+
2728def 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+
312358def 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 )
0 commit comments