⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 245500 in webkit


Ignore:
Timestamp:
May 18, 2019, 5:42:16 PM (7 years ago)
Author:
jiewen_tan@apple.com
Message:

[WebAuthN] Allow authenticators that support both CTAP and U2F to try U2F if CTAP fails in authenticatorGetAssertion
https://bugs.webkit.org/show_bug.cgi?id=197974
<rdar://problem/50879746>

Reviewed by Brent Fulgham.

Source/WebKit:

Authenticators that support both CTAP and U2F protocols can be used in a U2F enabled browser to create a credential in
U2F format. When such authenticator is used to login in WebKit, it will be treated as a CTAP authenticator. Since the
previous credential is in U2F format, the authenticator will not consider that as a valid credential when CTAP requests
come along for that U2F credential. Therefore the previous created U2F credential will not be asked at all, and users
will not be able to login. This situation is not well documented in the CTAP/WebAuthN spec yet.

To workaround the above issue, an authenticator that supports both protocols will be downgraded to a U2F authenticator
to ask a potential U2F credential once a valid error is returned regarding to the first CTAP request.

  • UIProcess/API/C/WKWebsiteDataStoreRef.cpp:

(WKWebsiteDataStoreSetWebAuthenticationMockConfiguration):

  • UIProcess/WebAuthentication/Authenticator.h:
  • UIProcess/WebAuthentication/AuthenticatorManager.cpp:

(WebKit::AuthenticatorManager::downgrade):

  • UIProcess/WebAuthentication/AuthenticatorManager.h:
  • UIProcess/WebAuthentication/Mock/MockHidConnection.cpp:

(WebKit::MockHidConnection::parseRequest):
(WebKit::MockHidConnection::feedReports):

  • UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h:
  • UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp:

(WebKit::CtapHidAuthenticator::makeCredential):
(WebKit::CtapHidAuthenticator::getAssertion):
(WebKit::CtapHidAuthenticator::continueGetAssertionAfterResponseReceived):
(WebKit::CtapHidAuthenticator::tryDowngrade):
(WebKit::CtapHidAuthenticator::continueGetAssertionAfterResponseReceived const): Deleted.

  • UIProcess/WebAuthentication/fido/CtapHidAuthenticator.h:

Tools:

Add a canDowngrade option for mock hid devices to simulate the situation.

  • WebKitTestRunner/InjectedBundle/TestRunner.cpp:

(WTR::TestRunner::setWebAuthenticationMockConfiguration):

LayoutTests:

  • http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https-expected.txt:
  • http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html:
  • http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt:
  • http/wpt/webauthn/public-key-credential-get-failure-hid.https.html:
  • http/wpt/webauthn/public-key-credential-get-success-u2f.https-expected.txt:
  • http/wpt/webauthn/public-key-credential-get-success-u2f.https.html:
  • http/wpt/webauthn/resources/util.js:
Location:
trunk
Files:
19 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r245494 r245500  
     12019-05-18  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthN] Allow authenticators that support both CTAP and U2F to try U2F if CTAP fails in authenticatorGetAssertion
     4        https://bugs.webkit.org/show_bug.cgi?id=197974
     5        <rdar://problem/50879746>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        * http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https-expected.txt:
     10        * http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html:
     11        * http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt:
     12        * http/wpt/webauthn/public-key-credential-get-failure-hid.https.html:
     13        * http/wpt/webauthn/public-key-credential-get-success-u2f.https-expected.txt:
     14        * http/wpt/webauthn/public-key-credential-get-success-u2f.https.html:
     15        * http/wpt/webauthn/resources/util.js:
     16
    1172019-05-17  Joonghun Park  <pjh0718@gmail.com>
    218
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https-expected.txt

    r238166 r245500  
    22PASS PublicKeyCredential's [[get]] with malicious payload in a mock hid authenticator.
    33PASS PublicKeyCredential's [[get]] with unsupported options in a mock hid authenticator.
     4PASS PublicKeyCredential's [[get]] with invalid credential in a mock hid authenticator.
     5PASS PublicKeyCredential's [[get]] with authenticator downgrade in a mock hid authenticator.
     6PASS PublicKeyCredential's [[get]] with authenticator downgrade in a mock hid authenticator. 2
    47
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid-silent.https.html

    r239752 r245500  
    3131        return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.");
    3232    }, "PublicKeyCredential's [[get]] with unsupported options in a mock hid authenticator.");
     33
     34    promise_test(function(t) {
     35        const options = {
     36            publicKey: {
     37                challenge: asciiToUint8Array("123456"),
     38                timeout: 10
     39            }
     40        };
     41
     42        if (window.testRunner)
     43            testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: [testCtapErrInvalidCredentialResponseBase64] } });
     44        return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.");
     45    }, "PublicKeyCredential's [[get]] with invalid credential in a mock hid authenticator.");
     46
     47    promise_test(function(t) {
     48        const options = {
     49            publicKey: {
     50                challenge: asciiToUint8Array("123456"),
     51                timeout: 10
     52            }
     53        };
     54
     55        if (window.testRunner)
     56            testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", canDowngrade: true, payloadBase64: [testCtapErrInvalidCredentialResponseBase64] } });
     57        return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.");
     58    }, "PublicKeyCredential's [[get]] with authenticator downgrade in a mock hid authenticator.");
     59
     60    promise_test(function(t) {
     61        const options = {
     62            publicKey: {
     63                challenge: asciiToUint8Array("123456"),
     64                extensions: { appid: "" },
     65                timeout: 10
     66            }
     67        };
     68
     69        if (window.testRunner)
     70            testRunner.setWebAuthenticationMockConfiguration({ silentFailure: true, hid: { stage: "request", subStage: "msg", error: "malicious-payload", canDowngrade: true, payloadBase64: [testCtapErrInvalidCredentialResponseBase64] } });
     71        return promiseRejects(t, "NotAllowedError", navigator.credentials.get(options), "Operation timed out.");
     72    }, "PublicKeyCredential's [[get]] with authenticator downgrade in a mock hid authenticator. 2");
    3373</script>
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https-expected.txt

    r238166 r245500  
    33PASS PublicKeyCredential's [[get]] with malicious payload in a mock hid authenticator.
    44PASS PublicKeyCredential's [[get]] with unsupported options in a mock hid authenticator.
     5PASS PublicKeyCredential's [[get]] with authenticator downgrade failed in a mock hid authenticator.
     6PASS PublicKeyCredential's [[get]] with authenticator downgrade succeeded and then U2F failed in a mock hid authenticator. 2
    57
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-failure-hid.https.html

    r239752 r245500  
    3434        if (window.testRunner)
    3535            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: [testDummyMessagePayloadBase64] } });
    36         return promiseRejects(t, "UnknownError", navigator.credentials.get(options), "Unknown internal error. Error code: -1");
     36        return promiseRejects(t, "UnknownError", navigator.credentials.get(options), "Unknown internal error. Error code: 255");
    3737    }, "PublicKeyCredential's [[get]] with malicious payload in a mock hid authenticator.");
    3838
     
    4949        return promiseRejects(t, "UnknownError", navigator.credentials.get(options), "Unknown internal error. Error code: 43");
    5050    }, "PublicKeyCredential's [[get]] with unsupported options in a mock hid authenticator.");
     51
     52    promise_test(function(t) {
     53        const options = {
     54            publicKey: {
     55                challenge: asciiToUint8Array("123456")
     56            }
     57        };
     58
     59        if (window.testRunner)
     60            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", payloadBase64: [testCtapErrInvalidCredentialResponseBase64] } });
     61        return promiseRejects(t, "UnknownError", navigator.credentials.get(options), "Unknown internal error. Error code: 34");
     62    }, "PublicKeyCredential's [[get]] with authenticator downgrade failed in a mock hid authenticator.");
     63
     64    promise_test(function(t) {
     65        const options = {
     66            publicKey: {
     67                challenge: asciiToUint8Array("123456"),
     68                extensions: { appid: "" }
     69            }
     70        };
     71
     72        if (window.testRunner)
     73            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "malicious-payload", canDowngrade: true, payloadBase64: [testCtapErrInvalidCredentialResponseBase64] } });
     74        return promiseRejects(t, "NotSupportedError", navigator.credentials.get(options), "Cannot convert the request to U2F command.");
     75    }, "PublicKeyCredential's [[get]] with authenticator downgrade succeeded and then U2F failed in a mock hid authenticator. 2");
    5176</script>
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-success-u2f.https-expected.txt

    r243193 r245500  
    99PASS PublicKeyCredential's [[get]] with multiple credentials and AppID is not used in a mock hid authenticator.
    1010PASS PublicKeyCredential's [[get]] with multiple credentials and AppID is used in a mock hid authenticator.
     11PASS PublicKeyCredential's [[get]] with downgraded authenticator in a mock hid authenticator.
     12PASS PublicKeyCredential's [[get]] with downgraded authenticator in a mock hid authenticator. (AppID)
    1113
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-get-success-u2f.https.html

    r243193 r245500  
    182182    }, "PublicKeyCredential's [[get]] with multiple credentials and AppID is used in a mock hid authenticator.");
    183183
     184    promise_test(t => {
     185        const options = {
     186            publicKey: {
     187                challenge: Base64URL.parse("MTIzNDU2"),
     188                allowCredentials: [{ type: "public-key", id: Base64URL.parse(testU2fCredentialIdBase64) }],
     189                timeout: 100,
     190                extensions: { appid: "https://localhost:666/appid" }
     191            }
     192        };
     193
     194        if (window.testRunner)
     195            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "success", canDowngrade: true, payloadBase64: [testCtapErrInvalidCredentialResponseBase64, testU2fSignResponse] } });
     196        return navigator.credentials.get(options).then(credential => {
     197            return checkResult(credential);
     198        });
     199    }, "PublicKeyCredential's [[get]] with downgraded authenticator in a mock hid authenticator.");
     200
     201    promise_test(t => {
     202        const options = {
     203            publicKey: {
     204                challenge: Base64URL.parse("MTIzNDU2"),
     205                allowCredentials: [{ type: "public-key", id: Base64URL.parse(testU2fCredentialIdBase64) }],
     206                timeout: 100,
     207                extensions: { appid: "https://localhost:666/appid" }
     208            }
     209        };
     210
     211        if (window.testRunner)
     212            testRunner.setWebAuthenticationMockConfiguration({ hid: { stage: "request", subStage: "msg", error: "success", canDowngrade: true, payloadBase64: [testCtapErrInvalidCredentialResponseBase64, testU2fApduWrongDataOnlyResponseBase64, testU2fSignResponse] } });
     213        return navigator.credentials.get(options).then(credential => {
     214            return checkResult(credential, true, "7eabc5cc3251bdc59115ef87b5f7ee74cb03747e39ba8341748565cc129c0719");
     215        });
     216    }, "PublicKeyCredential's [[get]] with downgraded authenticator in a mock hid authenticator. (AppID)");
     217
    184218</script>
  • trunk/LayoutTests/http/wpt/webauthn/resources/util.js

    r245262 r245500  
    9999    "f4V4LeEAhqeD0effTjY553H19q+jWq1Tc4WOkAA=";
    100100const testCtapErrCredentialExcludedOnlyResponseBase64 = "GQ==";
     101const testCtapErrInvalidCredentialResponseBase64 = "Ig==";
    101102
    102103const RESOURCES_DIR = "/WebKit/webauthn/resources/";
  • trunk/Source/WebKit/ChangeLog

    r245492 r245500  
     12019-05-18  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthN] Allow authenticators that support both CTAP and U2F to try U2F if CTAP fails in authenticatorGetAssertion
     4        https://bugs.webkit.org/show_bug.cgi?id=197974
     5        <rdar://problem/50879746>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Authenticators that support both CTAP and U2F protocols can be used in a U2F enabled browser to create a credential in
     10        U2F format. When such authenticator is used to login in WebKit, it will be treated as a CTAP authenticator. Since the
     11        previous credential is in U2F format, the authenticator will not consider that as a valid credential when CTAP requests
     12        come along for that U2F credential. Therefore the previous created U2F credential will not be asked at all, and users
     13        will not be able to login. This situation is not well documented in the CTAP/WebAuthN spec yet.
     14
     15        To workaround the above issue, an authenticator that supports both protocols will be downgraded to a U2F authenticator
     16        to ask a potential U2F credential once a valid error is returned regarding to the first CTAP request.
     17
     18        * UIProcess/API/C/WKWebsiteDataStoreRef.cpp:
     19        (WKWebsiteDataStoreSetWebAuthenticationMockConfiguration):
     20        * UIProcess/WebAuthentication/Authenticator.h:
     21        * UIProcess/WebAuthentication/AuthenticatorManager.cpp:
     22        (WebKit::AuthenticatorManager::downgrade):
     23        * UIProcess/WebAuthentication/AuthenticatorManager.h:
     24        * UIProcess/WebAuthentication/Mock/MockHidConnection.cpp:
     25        (WebKit::MockHidConnection::parseRequest):
     26        (WebKit::MockHidConnection::feedReports):
     27        * UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h:
     28        * UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp:
     29        (WebKit::CtapHidAuthenticator::makeCredential):
     30        (WebKit::CtapHidAuthenticator::getAssertion):
     31        (WebKit::CtapHidAuthenticator::continueGetAssertionAfterResponseReceived):
     32        (WebKit::CtapHidAuthenticator::tryDowngrade):
     33        (WebKit::CtapHidAuthenticator::continueGetAssertionAfterResponseReceived const): Deleted.
     34        * UIProcess/WebAuthentication/fido/CtapHidAuthenticator.h:
     35
    1362019-05-17  Don Olmstead  <don.olmstead@sony.com>
    237
  • trunk/Source/WebKit/UIProcess/API/C/WKWebsiteDataStoreRef.cpp

    r244572 r245500  
    653653            hid.continueAfterErrorData = WKBooleanGetValue(continueAfterErrorData);
    654654
     655        if (auto canDowngrade = static_cast<WKBooleanRef>(WKDictionaryGetItemForKey(hidRef, adoptWK(WKStringCreateWithUTF8CString("CanDowngrade")).get())))
     656            hid.canDowngrade = WKBooleanGetValue(canDowngrade);
     657
    655658        configuration.hid = WTFMove(hid);
    656659    }
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Authenticator.h

    r236481 r245500  
    4545        virtual ~Observer() = default;
    4646        virtual void respondReceived(Respond&&) = 0;
     47        virtual void downgrade(Authenticator* id, Ref<Authenticator>&& downgradedAuthenticator) = 0;
    4748    };
    4849
  • trunk/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp

    r245262 r245500  
    209209}
    210210
     211void AuthenticatorManager::downgrade(Authenticator* id, Ref<Authenticator>&& downgradedAuthenticator)
     212{
     213    RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), id] {
     214        if (!weakThis)
     215            return;
     216        auto removed = weakThis->m_authenticators.remove(id);
     217        ASSERT_UNUSED(removed, removed);
     218    });
     219    authenticatorAdded(WTFMove(downgradedAuthenticator));
     220}
     221
    211222UniqueRef<AuthenticatorTransportService> AuthenticatorManager::createService(WebCore::AuthenticatorTransport transport, AuthenticatorTransportService::Observer& observer) const
    212223{
  • trunk/Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h

    r245043 r245500  
    7171    // Authenticator::Observer
    7272    void respondReceived(Respond&&) final;
     73    void downgrade(Authenticator* id, Ref<Authenticator>&& downgradedAuthenticator) final;
    7374
    7475    // Overriden by MockAuthenticatorManager.
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockHidConnection.cpp

    r239852 r245500  
    133133    if (m_stage == Mock::Stage::Request && m_subStage == Mock::SubStage::Msg) {
    134134        // Make sure we issue different msg cmd for CTAP and U2F.
     135        if (m_configuration.hid->canDowngrade && !m_configuration.hid->isU2f)
     136            m_configuration.hid->isU2f = m_requestMessage->cmd() == FidoHidDeviceCommand::kMsg;
    135137        ASSERT(m_configuration.hid->isU2f ^ (m_requestMessage->cmd() != FidoHidDeviceCommand::kMsg));
    136138
     
    209211    Optional<FidoHidMessage> message;
    210212    if (m_stage == Mock::Stage::Info && m_subStage == Mock::SubStage::Msg) {
    211         auto infoData = encodeAsCBOR(AuthenticatorGetInfoResponse({ ProtocolVersion::kCtap }, Vector<uint8_t>(aaguidLength, 0u)));
     213        Vector<uint8_t> infoData;
     214        if (m_configuration.hid->canDowngrade)
     215            infoData = encodeAsCBOR(AuthenticatorGetInfoResponse({ ProtocolVersion::kCtap, ProtocolVersion::kU2f }, Vector<uint8_t>(aaguidLength, 0u)));
     216        else
     217            infoData = encodeAsCBOR(AuthenticatorGetInfoResponse({ ProtocolVersion::kCtap }, Vector<uint8_t>(aaguidLength, 0u)));
    212218        infoData.insert(0, static_cast<uint8_t>(CtapDeviceResponseCode::kSuccess)); // Prepend status code.
    213219        if (stagesMatch() && m_configuration.hid->error == Mock::Error::WrongChannelId)
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockWebAuthenticationConfiguration.h

    r239752 r245500  
    7070        bool fastDataArrival { false };
    7171        bool continueAfterErrorData { false };
     72        bool canDowngrade { false };
    7273    };
    7374
  • trunk/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.cpp

    r245262 r245500  
    3030
    3131#include "CtapHidDriver.h"
     32#include "U2fHidAuthenticator.h"
    3233#include <WebCore/DeviceRequestConverter.h>
    3334#include <WebCore/DeviceResponseConverter.h>
     
    5051void CtapHidAuthenticator::makeCredential()
    5152{
     53    ASSERT(!m_isDowngraded);
    5254    auto cborCmd = encodeMakeCredenitalRequestAsCBOR(requestData().hash, requestData().creationOptions, m_info.options().userVerificationAvailability());
    5355    m_driver->transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
     
    7577void CtapHidAuthenticator::getAssertion()
    7678{
     79    ASSERT(!m_isDowngraded);
    7780    auto cborCmd = encodeGetAssertionRequestAsCBOR(requestData().hash, requestData().requestOptions, m_info.options().userVerificationAvailability());
    7881    m_driver->transact(WTFMove(cborCmd), [weakThis = makeWeakPtr(*this)](Vector<uint8_t>&& data) {
     
    8487}
    8588
    86 void CtapHidAuthenticator::continueGetAssertionAfterResponseReceived(Vector<uint8_t>&& data) const
     89void CtapHidAuthenticator::continueGetAssertionAfterResponseReceived(Vector<uint8_t>&& data)
    8790{
    8891    auto response = readCTAPGetAssertionResponse(data);
    8992    if (!response) {
    90         receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", data.size() == 1 ? data[0] : -1) });
     93        auto error = getResponseCode(data);
     94        if (error != CtapDeviceResponseCode::kCtap2ErrInvalidCBOR && tryDowngrade())
     95            return;
     96        receiveRespond(ExceptionData { UnknownError, makeString("Unknown internal error. Error code: ", static_cast<uint8_t>(error)) });
    9197        return;
    9298    }
     
    94100}
    95101
     102bool CtapHidAuthenticator::tryDowngrade()
     103{
     104    if (m_info.versions().find(ProtocolVersion::kU2f) == m_info.versions().end())
     105        return false;
     106    if (!observer())
     107        return false;
     108
     109    m_isDowngraded = true;
     110    m_driver->setProtocol(ProtocolVersion::kU2f);
     111    observer()->downgrade(this, U2fHidAuthenticator::create(WTFMove(m_driver)));
     112    return true;
     113}
     114
    96115} // namespace WebKit
    97116
  • trunk/Source/WebKit/UIProcess/WebAuthentication/fido/CtapHidAuthenticator.h

    r238247 r245500  
    4848    void continueMakeCredentialAfterResponseReceived(Vector<uint8_t>&&) const;
    4949    void getAssertion() final;
    50     void continueGetAssertionAfterResponseReceived(Vector<uint8_t>&&) const;
     50    void continueGetAssertionAfterResponseReceived(Vector<uint8_t>&&);
     51
     52    bool tryDowngrade();
    5153
    5254    std::unique_ptr<CtapHidDriver> m_driver;
    5355    fido::AuthenticatorGetInfoResponse m_info;
     56    bool m_isDowngraded { false };
    5457};
    5558
  • trunk/Tools/ChangeLog

    r245499 r245500  
     12019-05-18  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthN] Allow authenticators that support both CTAP and U2F to try U2F if CTAP fails in authenticatorGetAssertion
     4        https://bugs.webkit.org/show_bug.cgi?id=197974
     5        <rdar://problem/50879746>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        Add a canDowngrade option for mock hid devices to simulate the situation.
     10
     11        * WebKitTestRunner/InjectedBundle/TestRunner.cpp:
     12        (WTR::TestRunner::setWebAuthenticationMockConfiguration):
     13
    1142019-05-18  Tadeu Zagallo  <tzagallo@apple.com>
    215
  • trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp

    r244898 r245500  
    26932693        }
    26942694
     2695        JSRetainPtr<JSStringRef> canDowngradePropertyName(Adopt, JSStringCreateWithUTF8CString("canDowngrade"));
     2696        JSValueRef canDowngradeValue = JSObjectGetProperty(context, hid, canDowngradePropertyName.get(), 0);
     2697        if (!JSValueIsUndefined(context, canDowngradeValue) && !JSValueIsNull(context, canDowngradeValue)) {
     2698            if (!JSValueIsBoolean(context, canDowngradeValue))
     2699                return;
     2700            bool canDowngrade = JSValueToBoolean(context, canDowngradeValue);
     2701            hidKeys.append(adoptWK(WKStringCreateWithUTF8CString("CanDowngrade")));
     2702            hidValues.append(adoptWK(WKBooleanCreate(canDowngrade)).get());
     2703        }
     2704
    26952705        Vector<WKStringRef> rawHidKeys;
    26962706        Vector<WKTypeRef> rawHidValues;
Note: See TracChangeset for help on using the changeset viewer.