Skip to content

Commit

Permalink
Send bogus registration if no credentials match for U2F authenticators
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=245904
<rdar://100329828>

Reviewed by Brent Fulgham.

U2F authenticators should wait for a tap before showing the no credentials status, just like
FIDO2 authenticators. This patch accomplishes that by sending a bogus register command whenever
there are no matching credentials from the allow list. The code already did that for registrations,
this starts doing it for assertions.

* Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.cpp:
(WebKit::U2fAuthenticator::issueSignCommand):
(WebKit::U2fAuthenticator::responseReceived):
(WebKit::U2fAuthenticator::continueCheckOnlyCommandAfterResponseReceived):
(WebKit::U2fAuthenticator::continueBogusCommandExcludeCredentialsMatchAfterResponseReceived):
(WebKit::U2fAuthenticator::continueBogusCommandNoCredentialsAfterResponseReceived):
(WebKit::U2fAuthenticator::continueBogusCommandAfterResponseReceived): Deleted.
* Source/WebKit/UIProcess/WebAuthentication/fido/U2fAuthenticator.h:
* LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-u2f-silent.https.html:
* LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-u2f.https.html:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-u2f-no-credentials.html:

Canonical link: https://commits.webkit.org/255273@main
  • Loading branch information
pascoej committed Oct 7, 2022
1 parent 3337a23 commit 848a805
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 16 deletions.
Expand Up @@ -28,7 +28,7 @@
};

if (window.internals)
internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64] } });
internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.");
}, "PublicKeyCredential's [[get]] with no matched allow credentials in a mock hid authenticator.");

Expand All @@ -42,7 +42,7 @@
};

if (window.internals)
internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64] } });
internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.");
}, "PublicKeyCredential's [[get]] with no matched allow credentials in a mock hid authenticator. 2");
</script>
Expand Up @@ -26,7 +26,7 @@
};

if (window.internals)
internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64] } });
internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "No credentials from the allowCredentials list is found in the authenticator.");
}, "PublicKeyCredential's [[get]] with no matched allow credentials in a mock hid authenticator.");

Expand All @@ -39,7 +39,7 @@
};

if (window.internals)
internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64] } });
internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "No credentials from the allowCredentials list is found in the authenticator.");
}, "PublicKeyCredential's [[get]] with no matched allow credentials in a mock hid authenticator. 2");

Expand All @@ -54,7 +54,7 @@
};

if (window.internals)
internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64] } });
internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "No credentials from the allowCredentials list is found in the authenticator.");
}, "PublicKeyCredential's [[get]] with no matched allow credentials in a mock hid authenticator. (AppID)");

Expand All @@ -68,7 +68,7 @@
};

if (window.internals)
internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64] } });
internals.setMockWebAuthenticationConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64, testU2fApduWrongDataOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "No credentials from the allowCredentials list is found in the authenticator.");
}, "PublicKeyCredential's [[get]] with no matched allow credentials in a mock hid authenticator. 2 (AppID)");
</script>
Expand Up @@ -99,9 +99,7 @@ void U2fAuthenticator::issueSignCommand(size_t index)
{
auto& requestOptions = std::get<PublicKeyCredentialRequestOptions>(requestData().options);
if (index >= requestOptions.allowCredentials.size()) {
if (auto* observer = this->observer())
observer->authenticatorStatusUpdated(WebAuthenticationStatus::NoCredentialsFound);
receiveRespond(ExceptionData { NotAllowedError, "No credentials from the allowCredentials list is found in the authenticator."_s });
issueNewCommand(constructBogusU2fRegistrationCommand(), CommandType::BogusCommandNoCredentials);
return;
}
auto u2fCmd = convertToU2fSignCommand(requestData().hash, requestOptions, requestOptions.allowCredentials[index].id, m_isAppId);
Expand Down Expand Up @@ -141,8 +139,11 @@ void U2fAuthenticator::responseReceived(Vector<uint8_t>&& response, CommandType
case CommandType::CheckOnlyCommand:
continueCheckOnlyCommandAfterResponseReceived(WTFMove(*apduResponse));
return;
case CommandType::BogusCommand:
continueBogusCommandAfterResponseReceived(WTFMove(*apduResponse));
case CommandType::BogusCommandExcludeCredentialsMatch:
continueBogusCommandExcludeCredentialsMatchAfterResponseReceived(WTFMove(*apduResponse));
return;
case CommandType::BogusCommandNoCredentials:
continueBogusCommandNoCredentialsAfterResponseReceived(WTFMove(*apduResponse));
return;
case CommandType::SignCommand:
continueSignCommandAfterResponseReceived(WTFMove(*apduResponse));
Expand Down Expand Up @@ -180,14 +181,14 @@ void U2fAuthenticator::continueCheckOnlyCommandAfterResponseReceived(ApduRespons
switch (apduResponse.status()) {
case ApduResponse::Status::SW_NO_ERROR:
case ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED:
issueNewCommand(constructBogusU2fRegistrationCommand(), CommandType::BogusCommand);
issueNewCommand(constructBogusU2fRegistrationCommand(), CommandType::BogusCommandExcludeCredentialsMatch);
return;
default:
checkExcludeList(m_nextListIndex++);
}
}

void U2fAuthenticator::continueBogusCommandAfterResponseReceived(ApduResponse&& apduResponse)
void U2fAuthenticator::continueBogusCommandExcludeCredentialsMatchAfterResponseReceived(ApduResponse&& apduResponse)
{
switch (apduResponse.status()) {
case ApduResponse::Status::SW_NO_ERROR:
Expand All @@ -202,6 +203,23 @@ void U2fAuthenticator::continueBogusCommandAfterResponseReceived(ApduResponse&&
}
}

void U2fAuthenticator::continueBogusCommandNoCredentialsAfterResponseReceived(ApduResponse&& apduResponse)
{
switch (apduResponse.status()) {
case ApduResponse::Status::SW_NO_ERROR:
if (auto* observer = this->observer())
observer->authenticatorStatusUpdated(WebAuthenticationStatus::NoCredentialsFound);
receiveRespond(ExceptionData { NotAllowedError, "No credentials from the allowCredentials list is found in the authenticator."_s });
return;
case ApduResponse::Status::SW_CONDITIONS_NOT_SATISFIED:
// Polling is required during test of user presence.
m_retryTimer.startOneShot(Seconds::fromMilliseconds(retryTimeOutValueMs));
return;
default:
receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<unsigned>(apduResponse.status())) });
}
}

void U2fAuthenticator::continueSignCommandAfterResponseReceived(ApduResponse&& apduResponse)
{
auto& requestOptions = std::get<PublicKeyCredentialRequestOptions>(requestData().options);
Expand Down
Expand Up @@ -57,7 +57,8 @@ class U2fAuthenticator final : public FidoAuthenticator {
enum class CommandType : uint8_t {
RegisterCommand,
CheckOnlyCommand,
BogusCommand,
BogusCommandExcludeCredentialsMatch,
BogusCommandNoCredentials,
SignCommand
};
void issueNewCommand(Vector<uint8_t>&& command, CommandType);
Expand All @@ -66,7 +67,8 @@ class U2fAuthenticator final : public FidoAuthenticator {
void responseReceived(Vector<uint8_t>&& response, CommandType);
void continueRegisterCommandAfterResponseReceived(apdu::ApduResponse&&);
void continueCheckOnlyCommandAfterResponseReceived(apdu::ApduResponse&&);
void continueBogusCommandAfterResponseReceived(apdu::ApduResponse&&);
void continueBogusCommandExcludeCredentialsMatchAfterResponseReceived(apdu::ApduResponse&&);
void continueBogusCommandNoCredentialsAfterResponseReceived(apdu::ApduResponse&&);
void continueSignCommandAfterResponseReceived(apdu::ApduResponse&&);

RunLoop::Timer<U2fAuthenticator> m_retryTimer;
Expand Down
@@ -1,8 +1,9 @@
<input type="text" id="input">
<script>
const testU2fApduWrongDataOnlyResponseBase64 = "aoA=";
const testU2fApduNoErrorOnlyResponseBase64 = "kAA=";
if (window.internals) {
internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64] } });
internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", isU2f: true, payloadBase64: [testU2fApduWrongDataOnlyResponseBase64, testU2fApduNoErrorOnlyResponseBase64] } });
internals.withUserGesture(() => { input.focus(); });
}

Expand Down

0 comments on commit 848a805

Please sign in to comment.