0

[WebAuthn] Record times for fetching conditional UI credentials

This adds a metric for recording the duration of platform credential
list requests to platforms. It splits the histograms for desktop,
Android GMS Core, and Android CredMan. It is only emitted when at
least one credential is found.

Low-Coverage-Reason: Only adding metrics to existing code paths.
Bug: 355006315
Change-Id: Idf6870600caafe435c6da1a0a02893451112bf1c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5738911
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Commit-Queue: Ken Buchanan <kenrb@chromium.org>
Reviewed-by: Martin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/main@{#1334382}
This commit is contained in:
Ken Buchanan
2024-07-29 19:14:51 +00:00
committed by Chromium LUCI CQ
parent e3fcfdcbd7
commit 6260d24243
9 changed files with 88 additions and 10 deletions
components/webauthn/android
java
junit
src
org
chromium
components
device/fido
tools/metrics/histograms/metadata/webauthn

@ -17,6 +17,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.util.Pair;
import androidx.annotation.Nullable;
@ -30,6 +31,7 @@ import org.jni_zero.NativeMethods;
import org.chromium.base.Callback;
import org.chromium.base.Log;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.blink.mojom.AuthenticatorStatus;
import org.chromium.blink.mojom.AuthenticatorTransport;
import org.chromium.blink.mojom.GetAssertionAuthenticatorResponse;
@ -533,6 +535,7 @@ public class Fido2CredentialRequest
mBarrier.resetAndSetWaitStatus(Barrier.Mode.ONLY_FIDO_2_API);
}
mConditionalUiState = ConditionalUiState.WAITING_FOR_CREDENTIAL_LIST;
long conditionalUiCredentialListInitialTimeMs = SystemClock.elapsedRealtime();
Fido2ApiCallHelper.getInstance()
.invokeFido2GetCredentials(
mAuthenticationContextProvider,
@ -544,7 +547,8 @@ public class Fido2CredentialRequest
options,
callerOriginString,
finalClientDataHash,
credentials)),
credentials,
conditionalUiCredentialListInitialTimeMs)),
(e) ->
mBarrier.onFido2ApiFailed(
AuthenticatorStatus.NOT_ALLOWED_ERROR));
@ -703,7 +707,8 @@ public class Fido2CredentialRequest
PublicKeyCredentialRequestOptions options,
String callerOriginString,
byte[] clientDataHash,
List<WebauthnCredentialDetails> credentials) {
List<WebauthnCredentialDetails> credentials,
long conditionalUiCredentialListInitialTimeMs) {
assert mConditionalUiState == ConditionalUiState.WAITING_FOR_CREDENTIAL_LIST
|| mConditionalUiState == ConditionalUiState.CANCEL_PENDING;
@ -712,6 +717,12 @@ public class Fido2CredentialRequest
boolean isConditionalRequest = options.isConditional;
assert isConditionalRequest || !hasAllowCredentials;
if (!credentials.isEmpty()) {
RecordHistogram.recordTimesHistogram(
"WebAuthentication.CredentialFetchDuration.GmsCore",
SystemClock.elapsedRealtime() - conditionalUiCredentialListInitialTimeMs);
}
if (mConditionalUiState == ConditionalUiState.CANCEL_PENDING) {
// The request was completed synchronously when the cancellation was received,
// so no need to return an error to the renderer.

@ -285,7 +285,8 @@ public class CredManHelper {
? CredManPrepareRequestEnum.SUCCESS_HAS_RESULTS
: CredManPrepareRequestEnum.SUCCESS_NO_RESULTS);
mMetricsHelper.recordCredmanPrepareRequestDuration(
SystemClock.elapsedRealtime() - startTimeMs);
SystemClock.elapsedRealtime() - startTimeMs,
hasPublicKeyCredentials);
}
};

@ -92,9 +92,13 @@ public class CredManMetricsHelper {
CredManPrepareRequestEnum.NUM_ENTRIES);
}
public void recordCredmanPrepareRequestDuration(long durationMs) {
public void recordCredmanPrepareRequestDuration(long durationMs, boolean credentialsFound) {
RecordHistogram.recordTimesHistogram(
"WebAuthentication.Android.CredManPrepareRequestDuration", durationMs);
if (credentialsFound) {
RecordHistogram.recordTimesHistogram(
"WebAuthentication.CredentialFetchDuration.CredMan", durationMs);
}
}
public void reportGetCredentialMetrics(

@ -511,7 +511,8 @@ public class CredManHelperRobolectricTest {
.recordCredmanPrepareRequestHistogram(eq(CredManPrepareRequestEnum.SENT_REQUEST));
verify(mMetricsHelper, times(1))
.recordCredmanPrepareRequestHistogram(eq(CredManPrepareRequestEnum.FAILURE));
verify(mMetricsHelper, times(0)).recordCredmanPrepareRequestDuration(anyLong());
verify(mMetricsHelper, times(0))
.recordCredmanPrepareRequestDuration(anyLong(), anyBoolean());
}
@Test
@ -579,7 +580,8 @@ public class CredManHelperRobolectricTest {
verify(mBarrier).onCredManSuccessful(credManCallSuccessfulRunback.capture());
credManCallSuccessfulRunback.getValue().run();
verify(mMetricsHelper, times(1)).recordCredmanPrepareRequestDuration(anyLong());
verify(mMetricsHelper, times(1))
.recordCredmanPrepareRequestDuration(anyLong(), anyBoolean());
// Setup the test for startGetRequest:
verify(mBrowserBridge, times(1))
@ -631,7 +633,8 @@ public class CredManHelperRobolectricTest {
verify(mBarrier).onCredManSuccessful(credManCallSuccessfulRunback.capture());
credManCallSuccessfulRunback.getValue().run();
verify(mMetricsHelper, times(1)).recordCredmanPrepareRequestDuration(anyLong());
verify(mMetricsHelper, times(1))
.recordCredmanPrepareRequestDuration(anyLong(), anyBoolean());
verify(mBrowserBridge, times(1))
.onCredManConditionalRequestPending(any(), anyBoolean(), callbackCaptor.capture());

@ -13,6 +13,7 @@
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
@ -42,6 +43,7 @@
namespace device {
namespace {
bool IsGpmPasskeyAuthenticator(const FidoAuthenticator& authenticator) {
switch (authenticator.GetType()) {
case AuthenticatorType::kWinNative:
@ -57,6 +59,31 @@ bool IsGpmPasskeyAuthenticator(const FidoAuthenticator& authenticator) {
}
NOTREACHED_NORETURN();
}
void MaybeRecordPlatformCredentialStatus(AuthenticatorType type,
base::TimeDelta elapsed_time) {
std::string metric_name;
switch (type) {
case AuthenticatorType::kWinNative:
metric_name = "WebAuthentication.CredentialFetchDuration.WinHello";
break;
case AuthenticatorType::kTouchID:
metric_name = "WebAuthentication.CredentialFetchDuration.TouchId";
break;
case AuthenticatorType::kChromeOS:
metric_name = "WebAuthentication.CredentialFetchDuration.ChromeOS";
break;
case AuthenticatorType::kICloudKeychain:
metric_name = "WebAuthentication.CredentialFetchDuration.ICloudKeychain";
break;
default:
return;
}
base::UmaHistogramTimes(metric_name, elapsed_time);
}
} // namespace
// TransportAvailabilityCallbackReadiness stores state that tracks whether
@ -532,8 +559,13 @@ void FidoRequestHandlerBase::GetPlatformCredentialStatus(
void FidoRequestHandlerBase::OnHavePlatformCredentialStatus(
AuthenticatorType authenticator_type,
std::optional<base::ElapsedTimer> timer,
std::vector<DiscoverableCredentialMetadata> creds,
RecognizedCredential has_credentials) {
if (creds.size() > 0 && timer.has_value()) {
MaybeRecordPlatformCredentialStatus(authenticator_type, timer->Elapsed());
}
if (authenticator_type == AuthenticatorType::kICloudKeychain) {
// iCloud Keychain is the second platform authenticator on the system and
// its status is reported via a different field.

@ -21,6 +21,7 @@
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_discovery_base.h"
@ -408,11 +409,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase
FidoAuthenticator* platform_authenticator);
// OnHavePlatformCredentialStatus is called by subclasses (after
// |GetPlatformCredentialStatus| has been called) to report on whether the
// `GetPlatformCredentialStatus` has been called) to report on whether the
// platform authenticator whether it has responsive discoverable credentials
// and whether it has responsive credentials at all.
// `timer` allows recording metrics with the wait time for this callback.
void OnHavePlatformCredentialStatus(
AuthenticatorType authenticator_type,
std::optional<base::ElapsedTimer> timer,
std::vector<DiscoverableCredentialMetadata> user_entities,
RecognizedCredential has_credentials);

@ -235,7 +235,7 @@ class FakeFidoRequestHandler : public FidoRequestHandlerBase {
void GetPlatformCredentialStatus(
FidoAuthenticator* platform_authenticator) override {
OnHavePlatformCredentialStatus(AuthenticatorType::kOther,
OnHavePlatformCredentialStatus(AuthenticatorType::kOther, std::nullopt,
/*user_entities=*/{},
has_platform_credential_);
}

@ -516,7 +516,8 @@ void GetAssertionRequestHandler::GetPlatformCredentialStatus(
request_, options_,
base::BindOnce(
&GetAssertionRequestHandler::OnHavePlatformCredentialStatus,
weak_factory_.GetWeakPtr(), platform_authenticator->GetType()));
weak_factory_.GetWeakPtr(), platform_authenticator->GetType(),
base::ElapsedTimer()));
}
bool GetAssertionRequestHandler::AuthenticatorSelectedForPINUVAuthToken(

@ -196,6 +196,29 @@ chromium-metrics-reviews@google.com.
</summary>
</histogram>
<histogram name="WebAuthentication.CredentialFetchDuration.{ApiUsed}"
units="ms" expires_after="2024-12-31">
<owner>kenrb@chromium.org</owner>
<owner>chrome-webauthn@google.com</owner>
<summary>
The duration of a request to obtain a list of credentials for a WebAuthn
conditional UI Get Assertion request, when using the {ApiUsed}. This is only
recorded when at least one credential is available in the results. This
complements the duration recorded by
WebAuthentication.Android.CredManPrepareRequestDuration, which only applies
to the CredMan API but emits on all requests including when there are no
results present.
</summary>
<token key="ApiUsed">
<variant name="ChromeOS" summary="ChromeOS platform API"/>
<variant name="CredMan" summary="Credential Manager API on Android"/>
<variant name="GmsCore" summary="GMS Core API on Android"/>
<variant name="ICloudKeychain" summary="ICloud Keychain API"/>
<variant name="TouchId" summary="Chrome-native Touch ID authenticator"/>
<variant name="WinHello" summary="Windows Hello API"/>
</token>
</histogram>
<histogram name="WebAuthentication.Enclave.ChangePinEvents"
enum="ChangePinEvent" expires_after="2024-12-31">
<owner>derinel@google.com</owner>