2121from typing import IO , AnyStr , Optional
2222
2323import httpx
24- import soft_webauthn
2524from cryptography .fernet import Fernet
2625from cryptography .hazmat .backends import default_backend
2726from cryptography .hazmat .primitives import hashes , serialization
3029from meatie import endpoint
3130from meatie_httpx import Client as MeatieClient
3231from pydantic import BaseModel , Field
33- from soft_webauthn import SoftWebauthnDevice
32+
33+ from soft_webauthn_patched import SoftWebauthnDevice
3434
3535__version__ = "2.1.0"
3636
@@ -258,7 +258,7 @@ def __init__(self, name: str):
258258 self .device_id = device_id
259259 self .device_meta = device_meta
260260 self .username = username
261- self .soft_webauthn_device = UserVerifiedDevice ()
261+ self .soft_webauthn_device = SoftWebauthnDevice ()
262262
263263 def dump (self , file : IO [bytes ], password : str = "" ):
264264 """Serializes passkey to file, encrypted with a password.
@@ -306,118 +306,6 @@ def load(cls, file: IO[AnyStr], password: str = "") -> Passkey:
306306 return passkey
307307
308308
309- class UserVerifiedDevice (SoftWebauthnDevice ):
310- """Software webauthn device with UV bit flag ALWAYS set."""
311-
312- def create (self , options , origin ):
313- """IDENTICAL to super().create(), except User Verification flag set."""
314- if {"alg" : - 7 , "type" : "public-key" } not in options ["publicKey" ][
315- "pubKeyCredParams"
316- ]:
317- raise ValueError (
318- "Requested pubKeyCredParams does not contain supported type"
319- )
320-
321- if ("attestation" in options ["publicKey" ]) and (
322- options ["publicKey" ]["attestation" ] not in [None , "none" ]
323- ):
324- raise ValueError ("Only none attestation supported" )
325-
326- # prepare new key
327- self .cred_init (
328- options ["publicKey" ]["rp" ]["id" ], options ["publicKey" ]["user" ]["id" ]
329- )
330-
331- # generate credential response
332- client_data = {
333- "type" : "webauthn.create" ,
334- "challenge" : soft_webauthn .urlsafe_b64encode (
335- options ["publicKey" ]["challenge" ]
336- )
337- .decode ("ascii" )
338- .rstrip ("=" ),
339- "origin" : origin ,
340- }
341-
342- rp_id_hash = soft_webauthn .sha256 (self .rp_id .encode ("ascii" ))
343- # Set Bit 2, User Verification (UV) also.
344- # https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Authenticator_data
345- flags = b"\x45 " # attested_data + user_present + user_verified
346- sign_count = soft_webauthn .pack (">I" , self .sign_count )
347- credential_id_length = soft_webauthn .pack (">H" , len (self .credential_id ))
348- cose_key = soft_webauthn .cbor .encode (
349- soft_webauthn .ES256 .from_cryptography_key (self .private_key .public_key ())
350- )
351- attestation_object = {
352- "authData" : rp_id_hash
353- + flags
354- + sign_count
355- + self .aaguid
356- + credential_id_length
357- + self .credential_id
358- + cose_key ,
359- "fmt" : "none" ,
360- "attStmt" : {},
361- }
362-
363- return {
364- "id" : soft_webauthn .urlsafe_b64encode (self .credential_id ),
365- "rawId" : self .credential_id ,
366- "response" : {
367- "clientDataJSON" : json .dumps (client_data ).encode ("utf-8" ),
368- "attestationObject" : soft_webauthn .cbor .encode (attestation_object ),
369- },
370- "type" : "public-key" ,
371- }
372-
373- def get (self , options , origin ):
374- """IDENTICAL to super().create(), except User Verification flag set."""
375-
376- if self .rp_id != options ["publicKey" ]["rpId" ]:
377- raise ValueError ("Requested rpID does not match current credential" )
378-
379- self .sign_count += 1
380-
381- # prepare signature
382- client_data = json .dumps (
383- {
384- "type" : "webauthn.get" ,
385- "challenge" : soft_webauthn .urlsafe_b64encode (
386- options ["publicKey" ]["challenge" ]
387- )
388- .decode ("ascii" )
389- .rstrip ("=" ),
390- "origin" : origin ,
391- }
392- ).encode ("utf-8" )
393- client_data_hash = soft_webauthn .sha256 (client_data )
394-
395- rp_id_hash = soft_webauthn .sha256 (self .rp_id .encode ("ascii" ))
396- # Set Bit 2, User Verification (UV) also.
397- # https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Authenticator_data
398- flags = b"\x05 "
399- sign_count = soft_webauthn .pack (">I" , self .sign_count )
400- authenticator_data = rp_id_hash + flags + sign_count
401-
402- signature = self .private_key .sign (
403- authenticator_data + client_data_hash ,
404- soft_webauthn .ec .ECDSA (soft_webauthn .hashes .SHA256 ()),
405- )
406-
407- # generate assertion
408- return {
409- "id" : soft_webauthn .urlsafe_b64encode (self .credential_id ),
410- "rawId" : self .credential_id ,
411- "response" : {
412- "authenticatorData" : authenticator_data ,
413- "clientDataJSON" : client_data ,
414- "signature" : signature ,
415- "userHandle" : self .user_handle ,
416- },
417- "type" : "public-key" ,
418- }
419-
420-
421309class Api (MeatieClient ):
422310 """A ubank API client.
423311
@@ -853,7 +741,7 @@ def add_passkey(username: str, password: str, passkey_name: str) -> Passkey:
853741 return passkey
854742
855743
856- def to_dict (device : UserVerifiedDevice ) -> dict :
744+ def to_dict (device : SoftWebauthnDevice ) -> dict :
857745 """Converts SoftWebauthnDevice instance to dict with serialized private key."""
858746 serialized_private_key = (
859747 device .private_key .private_bytes (
@@ -874,9 +762,9 @@ def to_dict(device: UserVerifiedDevice) -> dict:
874762 }
875763
876764
877- def from_dict (device_dict : dict ) -> UserVerifiedDevice :
765+ def from_dict (device_dict : dict ) -> SoftWebauthnDevice :
878766 """Returns device instantiated from dict."""
879- device = UserVerifiedDevice ()
767+ device = SoftWebauthnDevice ()
880768 device .credential_id = device_dict ["credential_id" ]
881769 device .private_key = serialization .load_pem_private_key (
882770 device_dict ["serialized_private_key" ],
0 commit comments