11import base64
22import json
3- import pickle
43import uuid
54from datetime import datetime , timedelta
65
76import pwnedpasswords
87from fido2 import cbor
9- from fido2 .client import ClientData
10- from fido2 .ctap2 import AuthenticatorData
8+ from fido2 .webauthn import AuthenticatorData
9+ from fido2 .webauthn import CollectedClientData as ClientData
1110from flask import Blueprint , abort , current_app , jsonify , request
1211from sqlalchemy .exc import IntegrityError
1312from sqlalchemy .orm .exc import NoResultFound
1716from app .clients .salesforce .salesforce_engagement import ENGAGEMENT_STAGE_ACTIVATION
1817from app .config import Config , QueueNames
1918from app .dao .fido2_key_dao import (
19+ _ensure_bytes ,
2020 create_fido2_session ,
2121 decode_and_register ,
2222 delete_fido2_key ,
23+ deserialize_fido2_key ,
2324 get_fido2_session ,
2425 list_fido2_keys ,
2526 save_fido2_key ,
@@ -809,7 +810,7 @@ def fido2_keys_user_register(user_id):
809810 user = get_user_and_accounts (user_id )
810811 keys = list_fido2_keys (user_id )
811812
812- credentials = list ( map ( lambda k : pickle . loads ( base64 . b64decode ( k . key )), keys ))
813+ credentials = [ deserialize_fido2_key ( k . key ) for k in keys ]
813814
814815 registration_data , state = Config .FIDO2_SERVER .register_begin (
815816 {
@@ -822,51 +823,81 @@ def fido2_keys_user_register(user_id):
822823 )
823824 create_fido2_session (user_id , state )
824825
825- # API Client only like JSON
826- return jsonify ({"data" : base64 .b64encode (cbor .encode (registration_data )).decode ("utf8" )})
826+ # In fido2 1.x, register_begin returns a PublicKeyCredentialCreationOptions object
827+ # We can encode it directly as CBOR - the object itself is CBOR-serializable
828+ registration_payload = base64 .b64encode (cbor .encode (registration_data )).decode ("utf8" )
829+ return jsonify ({"data" : registration_payload })
827830
828831
829832@user_blueprint .route ("/<uuid:user_id>/fido2_keys/authenticate" , methods = ["POST" ])
830833def fido2_keys_user_authenticate (user_id ):
831- keys = list_fido2_keys (user_id )
832- credentials = list (map (lambda k : pickle .loads (base64 .b64decode (k .key )), keys ))
834+ try :
835+ current_app .logger .info (f"Starting FIDO2 authentication for user { user_id } " )
836+ keys = list_fido2_keys (user_id )
837+ current_app .logger .info (f"Found { len (keys )} FIDO2 keys for user { user_id } " )
833838
834- auth_data , state = Config .FIDO2_SERVER .authenticate_begin (credentials )
835- create_fido2_session (user_id , state )
839+ if not keys :
840+ current_app .logger .warning (f"No FIDO2 keys found for user { user_id } " )
841+ return jsonify ({"error" : "No security keys registered" }), 400
836842
837- # API Client only like JSON
838- return jsonify ({"data" : base64 .b64encode (cbor .encode (auth_data )).decode ("utf8" )})
843+ credentials = [deserialize_fido2_key (k .key ) for k in keys ]
839844
845+ request_options , state = Config .FIDO2_SERVER .authenticate_begin (credentials )
846+ create_fido2_session (user_id , state )
847+ current_app .logger .info ("FIDO2 session created successfully" )
840848
841- @user_blueprint .route ("/<uuid:user_id>/fido2_keys/validate" , methods = ["POST" ])
842- def fido2_keys_user_validate (user_id ):
843- keys = list_fido2_keys (user_id )
844- credentials = list (map (lambda k : pickle .loads (base64 .b64decode (k .key )), keys ))
849+ # In fido2 1.x, authenticate_begin returns a CredentialRequestOptions object
850+ # We need to encode it directly as CBOR - the object itself is CBOR-serializable
851+ current_app .logger .info (f"Authentication challenge type: { type (request_options )} " )
845852
846- data = request .get_json ()
847- cbor_data = cbor .decode (base64 .b64decode (data ["payload" ]))
853+ # The request_options object can be CBOR encoded directly in fido2 1.x
854+ cbor_encoded = cbor .encode (request_options )
855+ current_app .logger .info (f"CBOR encoded length: { len (cbor_encoded )} " )
848856
849- credential_id = cbor_data ["credentialId" ]
850- client_data = ClientData (cbor_data ["clientDataJSON" ])
851- auth_data = AuthenticatorData (cbor_data ["authenticatorData" ])
852- signature = cbor_data ["signature" ]
857+ # Base64 encode for transmission
858+ auth_payload = base64 .b64encode (cbor_encoded ).decode ("utf8" )
853859
854- Config .FIDO2_SERVER .authenticate_complete (
855- get_fido2_session (user_id ),
856- credentials ,
857- credential_id ,
858- client_data ,
859- auth_data ,
860- signature ,
861- )
860+ return jsonify ({"data" : auth_payload })
861+ except Exception as e :
862+ current_app .logger .exception (f"Error in FIDO2 authentication for user { user_id } : { str (e )} " )
863+ return jsonify ({"error" : "An internal error has occurred" }), 500
862864
863- user_to_verify = get_user_by_id (user_id = user_id )
864- user_to_verify .current_session_id = str (uuid .uuid4 ())
865- user_to_verify .logged_in_at = datetime .utcnow ()
866- user_to_verify .failed_login_count = 0
867- save_model_user (user_to_verify )
868865
869- return jsonify ({"status" : "OK" })
866+ @user_blueprint .route ("/<uuid:user_id>/fido2_keys/validate" , methods = ["POST" ])
867+ def fido2_keys_user_validate (user_id ):
868+ try :
869+ current_app .logger .info (f"Starting FIDO2 validation for user { user_id } " )
870+ keys = list_fido2_keys (user_id )
871+ credentials = [deserialize_fido2_key (k .key ) for k in keys ]
872+
873+ data = request .get_json ()
874+ cbor_data = cbor .decode (base64 .b64decode (data ["payload" ]))
875+
876+ credential_id = _ensure_bytes (cbor_data ["credentialId" ])
877+ client_data = ClientData (_ensure_bytes (cbor_data ["clientDataJSON" ]))
878+ auth_data = AuthenticatorData (_ensure_bytes (cbor_data ["authenticatorData" ]))
879+ signature = _ensure_bytes (cbor_data ["signature" ])
880+
881+ Config .FIDO2_SERVER .authenticate_complete (
882+ get_fido2_session (user_id ),
883+ credentials ,
884+ credential_id ,
885+ client_data ,
886+ auth_data ,
887+ signature ,
888+ )
889+
890+ user_to_verify = get_user_by_id (user_id = user_id )
891+ user_to_verify .current_session_id = str (uuid .uuid4 ())
892+ user_to_verify .logged_in_at = datetime .utcnow ()
893+ user_to_verify .failed_login_count = 0
894+ save_model_user (user_to_verify )
895+
896+ current_app .logger .info (f"FIDO2 validation successful for user { user_id } " )
897+ return jsonify ({"status" : "OK" })
898+ except Exception as e :
899+ current_app .logger .exception (f"Error in FIDO2 validation for user { user_id } : { str (e )} " )
900+ return jsonify ({"error" : "An internal error occurred" }), 500
870901
871902
872903@user_blueprint .route ("/<uuid:user_id>/fido2_keys/<uuid:key_id>" , methods = ["DELETE" ])
0 commit comments