WebAuthn 不适用于 Chrome Android,但适用于桌面版和 iOS

问题描述 投票:0回答:1

我们最近使用 Django 2.1.7 将 WebAuthn 集成到我们的系统中。对于后端来说, 我不确定问题是什么。它在计算机和我们测试过的各种 iOS 设备上完美运行。我有一台配备 Android 14 和最新可用版本的 Chrome 的三星 S23。它还在 POCO 上进行了测试,并且返回了相同的负面结果。

我正在使用 WebAuthn 2.2.0,这就是我拥有端点的方式:

报名:

接受挑战:

def get_challenge_authn(request):
    try:
        rp_id     = "localhost"

        registration_options = generate_registration_options(
            rp_id     = rp_id,
            rp_name   = "MySite",
            user_name = user.username,
            user_display_name=user.username
        )
        
        if not registration_options:
            return JsonResponse({'status': 'error', 'msg': 'Ocurrio un error. Contacte a soporte'}, status=500)

        # Almacena temporalmente el challenge en la sesión del usuario.
        request.session['webauthn_challenge'] = base64.urlsafe_b64encode(registration_options.challenge).decode("utf-8").rstrip("=") 
        return JsonResponse(options_to_json(registration_options), safe=False)
    except Exception as e:
        return JsonResponse({'status': 'error', 'msg': e}, status=500)
    

验证并存储注册:

def verificar_guardar_registro_authn(request):    
    body = json.loads(request.body)
    try:

        credential_data = {
            "rawId": body['rawId'],
            "response": {
                "clientDataJSON": body['response']['clientDataJSON'],
                "attestationObject": body['response']['attestationObject'],
            },
            "type": body["type"],
            "id": body["id"],
        }
        stored_challenge = request.session.get('webauthn_challenge')
    
        if not stored_challenge:
            return JsonResponse({'status': 'error', 'msg': 'El challenge no se encuentra.'}, status=500)

        expected_rp_id     = "localhost"
        expected_origin    = "http://localhost:8000"

        verification = verify_registration_response(
            credential         = credential_data,
            expected_challenge = base64url_to_bytes(stored_challenge),
            expected_rp_id     = expected_rp_id,
            expected_origin    = expected_origin,
            require_user_verification = True
        )

        if verification.user_verified:
            credential = webAuthnCredential.objects.create(
                usuario       = request.user,
                credential_id = base64.urlsafe_b64encode(verification.credential_id).decode("utf-8").rstrip("="),
                public_key    = base64.urlsafe_b64encode(verification.credential_public_key).decode("utf-8").rstrip("="),
                sign_count    = verification.sign_count
            )

            return JsonResponse({'status': 'ok'}, status=201)
        else:
            return JsonResponse({'status': 'error', 'msg': 'Registro invalido'}, status=400)
    except Exception as e:
        print(traceback.format_exc())
        return JsonResponse({'status': 'error', 'msg': e}, status=500)

这很有效,因为我在 Android 手机上生成的密钥存储在我的服务器上。

这就是我使用服务的方式:我正在使用 SimpleWebAuthn 库

const registrar_autentificacion = async () => {
    try {
        const url = "{% url 'autentificacion:get_challenge_authn' %}";

        const response = await fetch(url);

        const options  = await response.json();
        const attResponse = await SimpleWebAuthnBrowser.startRegistration(JSON.parse(options));
        

        const url_verificacion = "{% url 'autentificacion:verificar_guardar_registro_authn' %}";

        const verificationResponse = await fetch(url_verificacion, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': '{{csrf_token}}'
          },
          body: JSON.stringify(attResponse)
        });

        if (verificationResponse.ok) {
          $('#modal_aviso_upload').modal('show');
          console.log('Registro exitoso.');
        } else {
          mensaje_error.innerText = "Ocurrio un error durante la verificación de la llave."
          mensaje_error.style.display = ''
          console.log('Error en la verificación.');
        }
    } catch (err) {
      mensaje_error.innerText = `Ocurrio un error durante la verificación de la llave.\nError: ${err}`;
      mensaje_error.style.display = ''
      console.error('Error en el registro:', err);
    }
  }

这些是我的登录代码:

接受挑战:

def get_challenge_authn_authentication(request):
    try:
        rp_id     = "localhost"

        authentication_options = generate_authentication_options(
            rp_id=rp_id,
            user_verification=UserVerificationRequirement.REQUIRED
        )
        request.session['webauthn_challenge'] = base64.urlsafe_b64encode(authentication_options.challenge).decode("utf-8").rstrip("=")
        return JsonResponse(options_to_json(authentication_options), safe=False)
    except Exception as e:
        print(e)
        return JsonResponse({'error': 'Error', 'msg': e}, safe=False, status=400)

以及验证(虽然我觉得这部分代码已经达不到了):

def verificar_guardar_autenticacion(request):
    auth_data = json.loads(request.body)

    stored_challenge = request.session.get('webauthn_challenge')
    if not stored_challenge:
        return JsonResponse({"error": "Challenge no encontrado en sesión"}, status=500)

    credential_id  = auth_data['id']
    credential = webAuthnCredential.objects.filter(credential_id=credential_id).first()

    if not credential:
        return JsonResponse({"error": "No se a encontrado la credencial de llave"}, status=404)

    expected_rp_id     = "localhost"
    expected_origin    = "http://localhost:8000"
    try:
        verification = verify_authentication_response(
            credential          = auth_data,
            expected_rp_id      = expected_rp_id, 
            expected_origin     = expected_origin,
            expected_challenge  = base64url_to_bytes(stored_challenge),
            credential_public_key = base64url_to_bytes(credential.public_key), 
            credential_current_sign_count = credential.sign_count
        )
        if verification.new_sign_count > credential.sign_count:
            credential.sign_count = verification.new_sign_count
            credential.save()

        # Login here
        return JsonResponse({"status": "Autenticación exitosa", 'autenticado': True})

    except Exception as e:
        print(traceback.format_exc())
        return JsonResponse({"error": "Fallo en la autenticación", "msg": str(e)}, status=400)

这是我的登录代码:

btn_login?.addEventListener('click', async (e) => {
        e.preventDefault();
        try {
            const url = "{% url 'autentificacion:get_challenge_authn_authentication' %}"
        
            const response = await fetch(url);
            const options = await response.json();

            const assertionResponse = await SimpleWebAuthnBrowser.startAuthentication(JSON.parse(options));

            const url_verificacion = "{% url 'autentificacion:verificar_guardar_autenticacion' %}";

            const verificationResponse = await fetch(url_verificacion, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRFToken': '{{csrf_token}}'
                },
                body: JSON.stringify(assertionResponse)
                });
            
            const result = await verificationResponse.json();
            const { autenticado = false, error = '', msg = '' } = result;
            if (autenticado) {
                window.location.href = "/";
            } else {
                mensaje_error.innerText     = `Ocurrio un error durante el inicio de sesión.\n${error}: ${msg}`;
                mensaje_error.style.display = ''
                console.log('Error en la verificación.');
            }

        } catch (error) {
            mensaje_error.innerText     = `Ocurrio un error durante el inicio de sesión.\nError: ${error}`;
            mensaje_error.style.display = ''
            console.error('Error en la autenticación:', error);
        }
    });

尝试登录时,我之前直接收到此消息:

Error message

当我更改我的首选服务(我最初有 Samsung Pass,现在我将其设置为 Google)后,我收到消息说没有注册密钥(我在更改服务后注册了密钥)。

No keys

python android webauthn google-chrome-android
1个回答
0
投票

很难根据该代码准确判断浏览器中最终会出现哪些 WebAuthn 选项,但请确保在创建过程中将

authenticatorSelection.requireResidentKey
spec 设置为 true。 Android 支持可发现和不可发现的凭据,而您需要前者。

如果创建后仍未发现凭据,请在 Google 密码管理器中检查它是否确实具有凭据。 (它隐藏在 Android 设置中的“密码和帐户”下。您还可以在桌面上的 Chrome 中登录同一 Google 帐户,然后查看 chrome://password-manager。)

© www.soinside.com 2019 - 2024. All rights reserved.