Changeset 257085 in webkit


Ignore:
Timestamp:
Feb 20, 2020 2:05:59 PM (4 years ago)
Author:
jiewen_tan@apple.com
Message:

[WebAuthn] Replace DeviceIdentity.framework
https://bugs.webkit.org/show_bug.cgi?id=207985
<rdar://problem/59369223>

Reviewed by Brent Fulgham.

Source/WebKit:

This patch replaces the DeviceIdentity.framework with a new framework that better suits our needs.
The new experimental authentication logic is handled by WebKtAdditions. Please refer to the radar
for detailed information.

Besides the replacement, this patch also:
1) changes how user consent is obtained to avoid multiple prompts for biometric input.
2) removes keychain workarounds for DeviceIdentity given the credential private key is now under our possession.
3) removes everything that is related to DeviceIdentity.

Covered by new tests within existing test files.

  • Configurations/WebKit.xcconfig:
  • Platform/spi/Cocoa/DeviceIdentitySPI.h: Removed.
  • UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h:
  • UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm:

(WebKit::LocalAuthenticatorInternal::toNSData):
(WebKit::LocalAuthenticator::makeCredential):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterUserConsented):
(WebKit::LocalAuthenticator::continueMakeCredentialAfterAttested):
(WebKit::LocalAuthenticator::getAssertion):
(WebKit::LocalAuthenticator::continueGetAssertionAfterUserConsented):

  • UIProcess/WebAuthentication/Cocoa/LocalConnection.h:
  • UIProcess/WebAuthentication/Cocoa/LocalConnection.mm:

(WebKit::LocalConnection::createCredentialPrivateKey const):
(WebKit::LocalConnection::getAttestation const):

  • UIProcess/WebAuthentication/Cocoa/LocalService.mm:

(WebKit::LocalService::isAvailable):

  • UIProcess/WebAuthentication/Mock/MockLocalConnection.h:
  • UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:

(WebKit::MockLocalConnection::createCredentialPrivateKey const):
(WebKit::MockLocalConnection::getAttestation const):

  • WebKit.xcodeproj/project.pbxproj:

Source/WTF:

  • wtf/PlatformHave.h:

LayoutTests:

  • http/wpt/webauthn/public-key-credential-create-failure-local.https-expected.txt:
  • http/wpt/webauthn/public-key-credential-create-failure-local.https.html:
  • http/wpt/webauthn/public-key-credential-create-success-local.https.html:
Location:
trunk
Files:
1 deleted
16 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r257082 r257085  
     12020-02-20  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthn] Replace DeviceIdentity.framework
     4        https://bugs.webkit.org/show_bug.cgi?id=207985
     5        <rdar://problem/59369223>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        * http/wpt/webauthn/public-key-credential-create-failure-local.https-expected.txt:
     10        * http/wpt/webauthn/public-key-credential-create-failure-local.https.html:
     11        * http/wpt/webauthn/public-key-credential-create-success-local.https.html:
     12
    1132020-02-20  Jason Lawrence  <lawrence.j@apple.com>
    214
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local.https-expected.txt

    r236842 r257085  
    44PASS PublicKeyCredential's [[create]] with matched exclude credentials in a mock local authenticator. 2nd
    55PASS PublicKeyCredential's [[create]] without user consent in a mock local authenticator.
     6PASS PublicKeyCredential's [[create]] without private keys in a mock local authenticator.
    67PASS PublicKeyCredential's [[create]] without attestation in a mock local authenticator.
    78PASS PublicKeyCredential's [[create]] deleting old credential in a mock local authenticator.
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-failure-local.https.html

    r254894 r257085  
    119119            if (window.internals)
    120120                internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false } });
     121            return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't create private key.");
     122        }, "PublicKeyCredential's [[create]] without private keys in a mock local authenticator.");
     123
     124        promise_test(t => {
     125            const options = {
     126                publicKey: {
     127                    rp: {
     128                        name: "example.com"
     129                    },
     130                    user: {
     131                        name: "John Appleseed",
     132                        id: Base64URL.parse(testUserhandleBase64),
     133                        displayName: "John",
     134                    },
     135                    challenge: asciiToUint8Array("123456"),
     136                    pubKeyCredParams: [{ type: "public-key", alg: -7 }]
     137                }
     138            };
     139            if (window.internals)
     140                internals.setMockWebAuthenticationConfiguration({ local: { acceptAuthentication: true, acceptAttestation: false, privateKeyBase64: privateKeyBase64 } });
    121141            return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't attest: The operation couldn't complete.");
    122142        }, "PublicKeyCredential's [[create]] without attestation in a mock local authenticator.");
     
    141161                testRunner.addTestKeyToKeychain(privateKeyBase64, testRpId, userhandleBase64);
    142162            }
    143             return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't attest: The operation couldn't complete.").then(() => {
     163            return promiseRejects(t, "UnknownError", navigator.credentials.create(options), "Couldn't create private key.").then(() => {
    144164                if (window.testRunner)
    145165                    assert_false(testRunner.keyExistsInKeychain(testRpId, userhandleBase64));
  • trunk/LayoutTests/http/wpt/webauthn/public-key-credential-create-success-local.https.html

    r250940 r257085  
    4242                assert_equals(attestationObject.fmt, "none");
    4343            else
    44                 assert_equals(attestationObject.fmt, "Apple");
     44                assert_equals(attestationObject.fmt, "apple");
    4545            // Check authData
    4646            const authData = decodeAuthData(attestationObject.authData);
     
    5959                assert_array_equals(attestationObject.attStmt.x5c[0], Base64URL.parse(testAttestationCertificateBase64));
    6060                assert_array_equals(attestationObject.attStmt.x5c[1], Base64URL.parse(testAttestationIssuingCACertificateBase64));
    61 
    62                 // Check signature
    63                 let publicKeyData = new Uint8Array(65);
    64                 publicKeyData[0] = 0x04;
    65                 publicKeyData.set(authData.publicKey['-2'], 1);
    66                 publicKeyData.set(authData.publicKey['-3'], 33);
    67                 return crypto.subtle.importKey("raw", publicKeyData, {
    68                     name: "ECDSA",
    69                     namedCurve: "P-256"
    70                 }, false, ['verify']).then(publicKey => {
    71                     return crypto.subtle.verify({
    72                         name: "ECDSA",
    73                         hash: "SHA-256"
    74                     }, publicKey, extractRawSignature(attestationObject.attStmt.sig), attestationObject.authData).then(verified => {
    75                         assert_true(verified);
    76                     });
    77                 });
    7861            }
    7962        }
  • trunk/Source/WTF/ChangeLog

    r257083 r257085  
     12020-02-20  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthn] Replace DeviceIdentity.framework
     4        https://bugs.webkit.org/show_bug.cgi?id=207985
     5        <rdar://problem/59369223>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        * wtf/PlatformHave.h:
     10
    1112020-02-20  Eric Liang  <ericliang@apple.com>
    212
  • trunk/Source/WTF/wtf/PlatformHave.h

    r257083 r257085  
    558558#endif
    559559
    560 #if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || (PLATFORM(IOS) && !PLATFORM(IOS_SIMULATOR))
    561 #define HAVE_DEVICE_IDENTITY 1
    562 #endif
    563 
    564560#if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101600) || (PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 140000)
    565561#define HAVE_COOKIE_CHANGE_LISTENER_API 1
  • trunk/Source/WebKit/ChangeLog

    r257083 r257085  
     12020-02-20  Jiewen Tan  <jiewen_tan@apple.com>
     2
     3        [WebAuthn] Replace DeviceIdentity.framework
     4        https://bugs.webkit.org/show_bug.cgi?id=207985
     5        <rdar://problem/59369223>
     6
     7        Reviewed by Brent Fulgham.
     8
     9        This patch replaces the DeviceIdentity.framework with a new framework that better suits our needs.
     10        The new experimental authentication logic is handled by WebKtAdditions. Please refer to the radar
     11        for detailed information.
     12
     13        Besides the replacement, this patch also:
     14        1) changes how user consent is obtained to avoid multiple prompts for biometric input.
     15        2) removes keychain workarounds for DeviceIdentity given the credential private key is now under our possession.
     16        3) removes everything that is related to DeviceIdentity.
     17
     18        Covered by new tests within existing test files.
     19
     20        * Configurations/WebKit.xcconfig:
     21        * Platform/spi/Cocoa/DeviceIdentitySPI.h: Removed.
     22        * UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h:
     23        * UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm:
     24        (WebKit::LocalAuthenticatorInternal::toNSData):
     25        (WebKit::LocalAuthenticator::makeCredential):
     26        (WebKit::LocalAuthenticator::continueMakeCredentialAfterUserConsented):
     27        (WebKit::LocalAuthenticator::continueMakeCredentialAfterAttested):
     28        (WebKit::LocalAuthenticator::getAssertion):
     29        (WebKit::LocalAuthenticator::continueGetAssertionAfterUserConsented):
     30        * UIProcess/WebAuthentication/Cocoa/LocalConnection.h:
     31        * UIProcess/WebAuthentication/Cocoa/LocalConnection.mm:
     32        (WebKit::LocalConnection::createCredentialPrivateKey const):
     33        (WebKit::LocalConnection::getAttestation const):
     34        * UIProcess/WebAuthentication/Cocoa/LocalService.mm:
     35        (WebKit::LocalService::isAvailable):
     36        * UIProcess/WebAuthentication/Mock/MockLocalConnection.h:
     37        * UIProcess/WebAuthentication/Mock/MockLocalConnection.mm:
     38        (WebKit::MockLocalConnection::createCredentialPrivateKey const):
     39        (WebKit::MockLocalConnection::getAttestation const):
     40        * WebKit.xcodeproj/project.pbxproj:
     41
    1422020-02-20  Eric Liang  <ericliang@apple.com>
    243
  • trunk/Source/WebKit/Configurations/WebKit.xcconfig

    r255189 r257085  
    6161WK_CORE_SERVICES_LDFLAGS_macosx = -framework CoreServices;
    6262
    63 WK_DEVICE_IDENTITY_LDFLAGS = $(WK_DEVICE_IDENTITY_LDFLAGS_$(WK_HAVE_DEVICE_IDENTITY));
    64 WK_DEVICE_IDENTITY_LDFLAGS_YES = -framework DeviceIdentity;
    65 
    6663WK_GRAPHICS_SERVICES_LDFLAGS = $(WK_GRAPHICS_SERVICES_LDFLAGS_$(WK_COCOA_TOUCH));
    6764WK_GRAPHICS_SERVICES_LDFLAGS_cocoatouch = -framework GraphicsServices;
     
    124121WK_AUTHKIT_LDFLAGS_MACOS_SINCE_1015 = -framework AuthKit;
    125122
    126 FRAMEWORK_AND_LIBRARY_LDFLAGS = -lobjc -framework CFNetwork -framework CoreAudio -framework CoreFoundation -framework CoreGraphics -framework CoreText -framework Foundation -framework ImageIO -framework IOKit -framework IOSurface -framework WebKitLegacy -lnetwork $(WK_ACCESSIBILITY_LDFLAGS) $(WK_APPKIT_LDFLAGS) $(WK_ASSERTION_SERVICES_LDFLAGS) $(WK_AUTHKIT_LDFLAGS) $(WK_CARBON_LDFLAGS) $(WK_CORE_PREDICTION_LDFLAGS) $(WK_CORE_SERVICES_LDFLAGS) $(WK_DEVICE_IDENTITY_LDFLAGS) $(WK_GRAPHICS_SERVICES_LDFLAGS) $(WK_LIBSANDBOX_LDFLAGS) $(WK_LIBWEBRTC_LDFLAGS) $(WK_MOBILE_CORE_SERVICES_LDFLAGS) $(WK_MOBILE_GESTALT_LDFLAGS) $(WK_OPENGL_LDFLAGS) $(WK_PDFKIT_LDFLAGS) $(WK_SAFE_BROWSING_LDFLAGS) $(WK_SECURITY_INTERFACE_LDFLAGS) $(WK_UIKIT_LDFLAGS) $(WK_URL_FORMATTING_LDFLAGS) $(WK_WEBINSPECTORUI_LDFLAGS);
     123FRAMEWORK_AND_LIBRARY_LDFLAGS = -lobjc -framework CFNetwork -framework CoreAudio -framework CoreFoundation -framework CoreGraphics -framework CoreText -framework Foundation -framework ImageIO -framework IOKit -framework IOSurface -framework WebKitLegacy -lnetwork $(WK_ACCESSIBILITY_LDFLAGS) $(WK_APPKIT_LDFLAGS) $(WK_ASSERTION_SERVICES_LDFLAGS) $(WK_AUTHKIT_LDFLAGS) $(WK_CARBON_LDFLAGS) $(WK_CORE_PREDICTION_LDFLAGS) $(WK_CORE_SERVICES_LDFLAGS) $(WK_GRAPHICS_SERVICES_LDFLAGS) $(WK_LIBSANDBOX_LDFLAGS) $(WK_LIBWEBRTC_LDFLAGS) $(WK_MOBILE_CORE_SERVICES_LDFLAGS) $(WK_MOBILE_GESTALT_LDFLAGS) $(WK_OPENGL_LDFLAGS) $(WK_PDFKIT_LDFLAGS) $(WK_SAFE_BROWSING_LDFLAGS) $(WK_SECURITY_INTERFACE_LDFLAGS) $(WK_UIKIT_LDFLAGS) $(WK_URL_FORMATTING_LDFLAGS) $(WK_WEBINSPECTORUI_LDFLAGS);
    127124
    128125// Prevent C++ standard library basic_stringstream, operator new, delete and their related exception types from being exported as weak symbols.
     
    162159WK_RELOCATABLE_FRAMEWORK_LDFLAGS_YES_macosx = -Wl,-not_for_dyld_shared_cache;
    163160
    164 WK_HAVE_DEVICE_IDENTITY = $(WK_HAVE_DEVICE_IDENTITY_$(WK_PLATFORM_NAME));
    165 WK_HAVE_DEVICE_IDENTITY_iphoneos = YES;
    166 WK_HAVE_DEVICE_IDENTITY_macosx = $(WK_HAVE_DEVICE_IDENTITY$(WK_MACOS_1014));
    167 WK_HAVE_DEVICE_IDENTITY_MACOS_SINCE_1014 = YES;
    168 
    169161WK_HAVE_URL_FORMATTING = $(WK_HAVE_URL_FORMATTING_$(WK_PLATFORM_NAME));
    170162WK_HAVE_URL_FORMATTING_iphoneos = $(WK_HAVE_URL_FORMATTING$(WK_IOS_12));
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.h

    r236481 r257085  
    5757
    5858    void makeCredential() final;
    59     void continueMakeCredentialAfterUserConsented(LocalConnection::UserConsent);
    60     void continueMakeCredentialAfterAttested(SecKeyRef, NSArray *certificates, NSError *);
     59    void continueMakeCredentialAfterUserConsented(SecAccessControlRef, LocalConnection::UserConsent, LAContext *);
     60    void continueMakeCredentialAfterAttested(SecKeyRef, Vector<uint8_t>&& credentialId, Vector<uint8_t>&& authData, NSArray *certificates, NSError *);
    6161
    6262    void getAssertion() final;
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalAuthenticator.mm

    r254894 r257085  
    8181}
    8282
     83static inline RetainPtr<NSData> toNSData(const Vector<uint8_t>& data)
     84{
     85    // FIXME(183534): Consider using initWithBytesNoCopy.
     86    return adoptNS([[NSData alloc] initWithBytes:data.data() length:data.size()]);
     87}
     88
    8389} // LocalAuthenticatorInternal
    8490
     
    100106    // Step 8 is implicitly captured by all UnknownError exception receiveResponds.
    101107    // Step 2.
    102     bool canFullfillPubKeyCredParams = false;
    103     for (auto& pubKeyCredParam : creationOptions.pubKeyCredParams) {
    104         if (pubKeyCredParam.type == PublicKeyCredentialType::PublicKey && pubKeyCredParam.alg == COSE::ES256) {
    105             canFullfillPubKeyCredParams = true;
    106             break;
    107         }
    108     }
    109     if (!canFullfillPubKeyCredParams) {
     108    if (notFound == creationOptions.pubKeyCredParams.findMatching([] (auto& pubKeyCredParam) {
     109        return pubKeyCredParam.type == PublicKeyCredentialType::PublicKey && pubKeyCredParam.alg == COSE::ES256;
     110    })) {
    110111        receiveRespond(ExceptionData { NotSupportedError, "The platform attached authenticator doesn't support any provided PublicKeyCredentialParameters."_s });
    111112        return;
     
    137138        auto retainAttributesArray = adoptCF(attributesArrayRef);
    138139
     140        // FIXME(rdar://problem/35900593): Need to obtain user consent and then return different error according to the result.
    139141        for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) {
    140142            NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel];
     
    149151    // FIXME(rdar://problem/35900593): Update to a formal UI.
    150152    // Get user consent.
    151     auto callback = [weakThis = makeWeakPtr(*this)](LocalConnection::UserConsent consent) {
     153    RetainPtr<SecAccessControlRef> accessControl;
     154    {
     155        CFErrorRef errorRef = nullptr;
     156        accessControl = adoptCF(SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence, &errorRef));
     157        auto retainError = adoptCF(errorRef);
     158        if (errorRef) {
     159            LOG_ERROR("Couldn't create access control: %@", (NSError *)errorRef);
     160            receiveRespond(ExceptionData { UnknownError, makeString("Couldn't create access control: ", String(((NSError*)errorRef).localizedDescription)) });
     161            return;
     162        }
     163    }
     164
     165    SecAccessControlRef accessControlRef = accessControl.get();
     166    auto callback = [accessControl = WTFMove(accessControl), weakThis = makeWeakPtr(*this)] (LocalConnection::UserConsent consent, LAContext *context) {
    152167        ASSERT(RunLoop::isMain());
    153168        if (!weakThis)
    154169            return;
    155170
    156         weakThis->continueMakeCredentialAfterUserConsented(consent);
     171        weakThis->continueMakeCredentialAfterUserConsented(accessControl.get(), consent, context);
    157172    };
    158173    m_connection->getUserConsent(
    159         "allow " + creationOptions.rp.id + " to create a public key credential for " + creationOptions.user.name,
     174        makeString("allow "_s, creationOptions.rp.id, " to create a public key credential for "_s, creationOptions.user.name),
     175        accessControlRef,
    160176        WTFMove(callback));
    161177}
    162178
    163 void LocalAuthenticator::continueMakeCredentialAfterUserConsented(LocalConnection::UserConsent consent)
    164 {
     179void LocalAuthenticator::continueMakeCredentialAfterUserConsented(SecAccessControlRef accessControlRef, LocalConnection::UserConsent consent, LAContext *context)
     180{
     181    using namespace LocalAuthenticatorInternal;
     182
    165183    ASSERT(m_state == State::RequestReceived);
    166184    m_state = State::UserConsented;
     
    172190    }
    173191
     192    // FIXME(183533): A single kSecClassKey item couldn't store all meta data. The following schema is a tentative solution
     193    // to accommodate the most important meta data, i.e. RP ID, Credential ID, and userhandle.
     194    // kSecAttrLabel: RP ID
     195    // kSecAttrApplicationLabel: Credential ID (auto-gen by Keychain)
     196    // kSecAttrApplicationTag: userhandle
     197    // Noted, the vale of kSecAttrApplicationLabel is automatically generated by the Keychain, which is a SHA-1 hash of
     198    // the public key. We borrow it directly for now to workaround the stated limitations.
     199    const auto& secAttrLabel = creationOptions.rp.id;
     200    auto secAttrApplicationTag = toNSData(creationOptions.user.idVector);
     201
    174202    // Step 7.5.
    175     // Userhandle is stored in kSecAttrApplicationTag attribute.
    176203    // Failures after this point could block users' accounts forever. Should we follow the spec?
    177204    NSDictionary* deleteQuery = @{
    178205        (id)kSecClass: (id)kSecClassKey,
    179         (id)kSecAttrLabel: creationOptions.rp.id,
    180         (id)kSecAttrApplicationTag: [NSData dataWithBytes:creationOptions.user.idVector.data() length:creationOptions.user.idVector.size()],
     206        (id)kSecAttrLabel: secAttrLabel,
     207        (id)kSecAttrApplicationTag: secAttrApplicationTag.get(),
    181208#if HAVE(DATA_PROTECTION_KEYCHAIN)
    182209        (id)kSecUseDataProtectionKeychain: @YES
     
    192219    }
    193220
    194     // Step 7.1, 13. Apple Attestation
    195     auto callback = [weakThis = makeWeakPtr(*this)](SecKeyRef _Nullable privateKey, NSArray * _Nullable certificates, NSError * _Nullable error) {
    196         ASSERT(RunLoop::isMain());
    197         if (!weakThis)
    198             return;
    199         weakThis->continueMakeCredentialAfterAttested(privateKey, certificates, error);
    200     };
    201     m_connection->getAttestation(creationOptions.rp.id, creationOptions.user.name, requestData().hash, WTFMove(callback));
    202 }
    203 
    204 void LocalAuthenticator::continueMakeCredentialAfterAttested(SecKeyRef privateKey, NSArray *certificates, NSError *error)
    205 {
    206     using namespace LocalAuthenticatorInternal;
    207 
    208     ASSERT(m_state == State::UserConsented);
    209     m_state = State::Attested;
    210     auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
    211 
    212     if (error) {
    213         LOG_ERROR("Couldn't attest: %@", error);
    214         receiveRespond(ExceptionData { UnknownError, makeString("Couldn't attest: ", String(error.localizedDescription)) });
    215         return;
    216     }
    217     // Attestation Certificate and Attestation Issuing CA
    218     ASSERT(certificates && ([certificates count] == 2));
    219 
    220     // Step 7.2-7.4.
    221     // FIXME(183533): A single kSecClassKey item couldn't store all meta data. The following schema is a tentative solution
    222     // to accommodate the most important meta data, i.e. RP ID, Credential ID, and userhandle.
    223     // kSecAttrLabel: RP ID
    224     // kSecAttrApplicationLabel: Credential ID (auto-gen by Keychain)
    225     // kSecAttrApplicationTag: userhandle
    226     // Noted, the current DeviceIdentity.Framework would only allow us to pass the kSecAttrLabel as the inital attribute
    227     // for the Keychain item. Since that's the only clue we have to locate the unique item, we use the pattern username@rp.id
    228     // as the initial value.
    229     // Also noted, the vale of kSecAttrApplicationLabel is automatically generated by the Keychain, which is a SHA-1 hash of
    230     // the public key. We borrow it directly for now to workaround the stated limitations.
    231     // Update the Keychain item to the above schema.
    232     // FIXME(183533): DeviceIdentity.Framework would insert certificates into Keychain as well. We should update those as well.
     221    // Step 7.1-7.4.
     222    // The above-to-create private key will be inserted into keychain while using SEP.
     223    auto privateKey = m_connection->createCredentialPrivateKey(context, accessControlRef, secAttrLabel, secAttrApplicationTag.get());
     224    if (!privateKey) {
     225        receiveRespond(ExceptionData { UnknownError, "Couldn't create private key."_s });
     226        return;
     227    }
     228
    233229    Vector<uint8_t> credentialId;
    234230    {
    235         // -rk-ucrt is added by DeviceIdentity.Framework.
    236         String label = makeString(creationOptions.user.name, "@", creationOptions.rp.id, "-rk-ucrt");
    237231        NSDictionary *credentialIdQuery = @{
    238232            (id)kSecClass: (id)kSecClassKey,
    239233            (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
    240             (id)kSecAttrLabel: label,
     234            (id)kSecAttrLabel: secAttrLabel,
     235            (id)kSecAttrApplicationTag: secAttrApplicationTag.get(),
    241236            (id)kSecReturnAttributes: @YES,
    242237#if HAVE(DATA_PROTECTION_KEYCHAIN)
     
    257252        NSDictionary *nsAttributes = (NSDictionary *)attributesRef;
    258253        credentialId = toVector(nsAttributes[(id)kSecAttrApplicationLabel]);
    259 
    260         NSDictionary *updateQuery = @{
    261             (id)kSecClass: (id)kSecClassKey,
    262             (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
    263             (id)kSecAttrApplicationLabel: nsAttributes[(id)kSecAttrApplicationLabel],
    264 #if HAVE(DATA_PROTECTION_KEYCHAIN)
    265             (id)kSecUseDataProtectionKeychain: @YES
    266 #else
    267             (id)kSecAttrNoLegacy: @YES
    268 #endif
    269         };
    270         NSDictionary *updateParams = @{
    271             (id)kSecAttrLabel: creationOptions.rp.id,
    272             (id)kSecAttrApplicationTag: [NSData dataWithBytes:creationOptions.user.idVector.data() length:creationOptions.user.idVector.size()],
    273         };
    274         status = SecItemUpdate((__bridge CFDictionaryRef)updateQuery, (__bridge CFDictionaryRef)updateParams);
    275         if (status) {
    276             LOG_ERROR("Couldn't update the Keychain item: %d", status);
    277             receiveRespond(ExceptionData { UnknownError, makeString("Couldn't update the Keychain item: ", status) });
    278             return;
    279         }
    280254    }
    281255
     
    290264        RetainPtr<CFDataRef> publicKeyDataRef;
    291265        {
    292             auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey));
     266            auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey.get()));
    293267            CFErrorRef errorRef = nullptr;
    294268            publicKeyDataRef = adoptCF(SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef));
     
    315289    auto authData = buildAuthData(creationOptions.rp.id, makeCredentialFlags, counter, attestedCredentialData);
    316290
     291    // Step 13. Apple Attestation
     292    auto *privateKeyRef = privateKey.get();
     293    auto nsAuthData = toNSData(authData);
     294    auto callback = [privateKey = WTFMove(privateKey), credentialId = WTFMove(credentialId), authData = WTFMove(authData), weakThis = makeWeakPtr(*this)] (NSArray * _Nullable certificates, NSError * _Nullable error) mutable {
     295        ASSERT(RunLoop::isMain());
     296        if (!weakThis)
     297            return;
     298        weakThis->continueMakeCredentialAfterAttested(privateKey.get(), WTFMove(credentialId), WTFMove(authData), certificates, error);
     299    };
     300    m_connection->getAttestation(privateKeyRef, nsAuthData.get(), toNSData(requestData().hash).get(), WTFMove(callback));
     301}
     302
     303void LocalAuthenticator::continueMakeCredentialAfterAttested(SecKeyRef privateKey, Vector<uint8_t>&& credentialId, Vector<uint8_t>&& authData, NSArray *certificates, NSError *error)
     304{
     305    using namespace LocalAuthenticatorInternal;
     306
     307    ASSERT(m_state == State::UserConsented);
     308    m_state = State::Attested;
     309    auto& creationOptions = WTF::get<PublicKeyCredentialCreationOptions>(requestData().options);
     310
     311    if (error) {
     312        LOG_ERROR("Couldn't attest: %@", error);
     313        receiveRespond(ExceptionData { UnknownError, makeString("Couldn't attest: ", String(error.localizedDescription)) });
     314        return;
     315    }
     316    // Attestation Certificate and Attestation Issuing CA
     317    ASSERT(certificates && ([certificates count] == 2));
     318
    317319    // Step 13. Apple Attestation Cont'
    318320    // Assemble the attestation object:
     
    320322    cbor::CBORValue::MapValue attestationStatementMap;
    321323    {
    322         Vector<uint8_t> signature;
    323         {
    324             CFErrorRef errorRef = nullptr;
    325             // FIXME(183652): Reduce prompt for biometrics
    326             auto signatureRef = adoptCF(SecKeyCreateSignature(privateKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)[NSData dataWithBytes:authData.data() length:authData.size()], &errorRef));
    327             auto retainError = adoptCF(errorRef);
    328             if (errorRef) {
    329                 LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef);
    330                 receiveRespond(ExceptionData { UnknownError, makeString("Couldn't generate the signature: ", String(((NSError*)errorRef).localizedDescription)) });
    331                 return;
    332             }
    333             signature = toVector((NSData *)signatureRef.get());
    334         }
    335324        attestationStatementMap[cbor::CBORValue("alg")] = cbor::CBORValue(COSE::ES256);
    336         attestationStatementMap[cbor::CBORValue("sig")] = cbor::CBORValue(signature);
    337325        Vector<cbor::CBORValue> cborArray;
    338326        for (size_t i = 0; i < [certificates count]; i++)
     
    340328        attestationStatementMap[cbor::CBORValue("x5c")] = cbor::CBORValue(WTFMove(cborArray));
    341329    }
    342     auto attestationObject = buildAttestationObject(WTFMove(authData), "Apple", WTFMove(attestationStatementMap), creationOptions.attestation);
     330    auto attestationObject = buildAttestationObject(WTFMove(authData), "apple", WTFMove(attestationStatementMap), creationOptions.attestation);
    343331
    344332    receiveRespond(AuthenticatorAttestationResponse::create(credentialId, attestationObject));
     
    357345    // Step 12 is implicitly captured by all UnknownError exception callbacks.
    358346    // Step 3-5. Unlike the spec, if an allow list is provided and there is no intersection between existing ones and the allow list, we always return NotAllowedError.
     347    // FIXME(rdar://problem/35900593): Need to inform users.
    359348    auto allowCredentialIds = produceHashSet(requestOptions.allowCredentials);
    360349    if (!requestOptions.allowCredentials.isEmpty() && allowCredentialIds.isEmpty()) {
     
    449438            (id)kSecClass: (id)kSecClassKey,
    450439            (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
    451             (id)kSecAttrApplicationLabel: [NSData dataWithBytes:credentialId.data() length:credentialId.size()],
     440            (id)kSecAttrApplicationLabel: toNSData(credentialId).get(),
    452441            (id)kSecUseAuthenticationContext: context,
    453442            (id)kSecReturnRef: @YES,
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.h

    r250249 r257085  
    5252    };
    5353
    54     using AttestationCallback = CompletionHandler<void(SecKeyRef, NSArray *, NSError *)>;
     54    using AttestationCallback = CompletionHandler<void(NSArray *, NSError *)>;
    5555    using UserConsentCallback = CompletionHandler<void(UserConsent)>;
    5656    using UserConsentContextCallback = CompletionHandler<void(UserConsent, LAContext *)>;
     
    6060
    6161    // Overrided by MockLocalConnection.
    62     virtual void getUserConsent(const String& reason, UserConsentCallback&&) const;
    6362    virtual void getUserConsent(const String& reason, SecAccessControlRef, UserConsentContextCallback&&) const;
    64     virtual void getAttestation(const String& rpId, const String& username, const Vector<uint8_t>& hash, AttestationCallback&&) const;
     63    virtual RetainPtr<SecKeyRef> createCredentialPrivateKey(LAContext *, SecAccessControlRef, const String& secAttrLabel, NSData *secAttrApplicationTag) const;
     64    virtual void getAttestation(SecKeyRef, NSData *authData, NSData *hash, AttestationCallback&&) const;
    6565    virtual NSDictionary *selectCredential(const NSArray *) const;
    6666};
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalConnection.mm

    r250249 r257085  
    2929#if ENABLE(WEB_AUTHN)
    3030
    31 #import "DeviceIdentitySPI.h"
    32 #import <WebCore/ExceptionData.h>
    3331#import <wtf/BlockPtr.h>
    3432#import <wtf/RunLoop.h>
     33
     34#if USE(APPLE_INTERNAL_SDK)
     35#import <WebKitAdditions/LocalConnectionAdditions.h>
     36#endif
    3537
    3638#import "LocalAuthenticationSoftLink.h"
    3739
    3840namespace WebKit {
    39 
    40 void LocalConnection::getUserConsent(const String& reason, UserConsentCallback&& completionHandler) const
    41 {
    42     auto context = adoptNS([allocLAContextInstance() init]);
    43     auto reply = makeBlockPtr([completionHandler = WTFMove(completionHandler)] (BOOL success, NSError *error) mutable {
    44         ASSERT(!RunLoop::isMain());
    45 
    46         UserConsent consent = UserConsent::Yes;
    47         if (!success || error) {
    48             LOG_ERROR("Couldn't authenticate with biometrics: %@", error);
    49             consent = UserConsent::No;
    50         }
    51         RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), consent]() mutable {
    52             completionHandler(consent);
    53         });
    54     });
    55     [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:reason reply:reply.get()];
    56 }
    5741
    5842void LocalConnection::getUserConsent(const String& reason, SecAccessControlRef accessControl, UserConsentContextCallback&& completionHandler) const
     
    7458}
    7559
    76 void LocalConnection::getAttestation(const String& rpId, const String& username, const Vector<uint8_t>& hash, AttestationCallback&& completionHandler) const
     60RetainPtr<SecKeyRef> LocalConnection::createCredentialPrivateKey(LAContext *context, SecAccessControlRef accessControlRef, const String& secAttrLabel, NSData *secAttrApplicationTag) const
    7761{
    78 #if HAVE(DEVICE_IDENTITY)
    79     // Apple Attestation
    80     ASSERT(hash.size() <= 32);
     62    NSDictionary *attributes = @{
     63        (id)kSecAttrTokenID: (id)kSecAttrTokenIDSecureEnclave,
     64        (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
     65        (id)kSecAttrKeySizeInBits: @256,
     66        (id)kSecPrivateKeyAttrs: @{
     67            (id)kSecUseAuthenticationContext: context,
     68            (id)kSecAttrAccessControl: (id)accessControlRef,
     69            (id)kSecAttrIsPermanent: @YES,
     70            (id)kSecAttrAccessGroup: @"com.apple.webkit.webauthn",
     71            (id)kSecAttrLabel: secAttrLabel,
     72            (id)kSecAttrApplicationTag: secAttrApplicationTag,
     73        }};
     74    CFErrorRef errorRef = nullptr;
     75    auto credentialPrivateKey = adoptCF(SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &errorRef));
     76    auto retainError = adoptCF(errorRef);
     77    if (errorRef) {
     78        LOG_ERROR("Couldn't create private key: %@", (NSError *)errorRef);
     79        return nullptr;
     80    }
     81    return credentialPrivateKey;
     82}
    8183
    82     RetainPtr<SecAccessControlRef> accessControlRef;
    83     {
    84         CFErrorRef errorRef = nullptr;
    85         accessControlRef = adoptCF(SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence, &errorRef));
    86         auto retainError = adoptCF(errorRef);
    87         if (errorRef) {
    88             LOG_ERROR("Couldn't create ACL: %@", (NSError *)errorRef);
    89             completionHandler(NULL, NULL, [NSError errorWithDomain:@"com.apple.WebKit.WebAuthN" code:1 userInfo:nil]);
    90             return;
    91         }
    92     }
    93 
    94     String label = makeString(username, "@", rpId);
    95     NSDictionary *options = @{
    96         kMAOptionsBAAKeychainLabel: label,
    97         // FIXME(rdar://problem/38489134): Need a formal name.
    98         kMAOptionsBAAKeychainAccessGroup: @"com.apple.safari.WebAuthN.credentials",
    99         kMAOptionsBAAIgnoreExistingKeychainItems: @YES,
    100         // FIXME(rdar://problem/38489134): Determine a proper lifespan.
    101         kMAOptionsBAAValidity: @(1440), // Last one day.
    102         kMAOptionsBAASCRTAttestation: @NO,
    103         kMAOptionsBAANonce: [NSData dataWithBytes:hash.data() length:hash.size()],
    104         kMAOptionsBAAAccessControls: (id)accessControlRef.get(),
    105         kMAOptionsBAAOIDSToInclude: @[kMAOptionsBAAOIDNonce]
    106     };
    107 
    108     // FIXME(183652): Reduce prompt for biometrics
    109     DeviceIdentityIssueClientCertificateWithCompletion(dispatch_get_main_queue(), options, makeBlockPtr(WTFMove(completionHandler)).get());
    110 #endif // HAVE(DEVICE_IDENTITY)
     84void LocalConnection::getAttestation(SecKeyRef privateKey, NSData *authData, NSData *hash, AttestationCallback&& completionHandler) const
     85{
     86#if defined(LOCALCONNECTION_ADDITIONS)
     87LOCALCONNECTION_ADDITIONS
     88#endif
    11189}
    11290
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Cocoa/LocalService.mm

    r251041 r257085  
    3232#import "LocalConnection.h"
    3333
     34#if USE(APPLE_INTERNAL_SDK)
     35#import <WebKitAdditions/LocalServiceAdditions.h>
     36#endif
     37
    3438#import "LocalAuthenticationSoftLink.h"
    3539
     
    4145}
    4246
    43 // FIXME(rdar://problem/51048542)
    4447bool LocalService::isAvailable()
    4548{
     
    5053        return false;
    5154    }
     55
     56#if defined(LOCALSERVICE_ADDITIONS)
     57LOCALSERVICE_ADDITIONS
     58#endif
     59
    5260    return true;
    5361}
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.h

    r250940 r257085  
    3838
    3939private:
    40     void getUserConsent(const String& reason, UserConsentCallback&&) const final;
    4140    void getUserConsent(const String& reason, SecAccessControlRef, UserConsentContextCallback&&) const final;
    42     void getAttestation(const String& rpId, const String& username, const Vector<uint8_t>& hash, AttestationCallback&&) const final;
     41    RetainPtr<SecKeyRef> createCredentialPrivateKey(LAContext *, SecAccessControlRef, const String& secAttrLabel, NSData *secAttrApplicationTag) const final;
     42    void getAttestation(SecKeyRef, NSData *authData, NSData *hash, AttestationCallback&&) const final;
    4343    NSDictionary *selectCredential(const NSArray *) const final;
    4444
  • trunk/Source/WebKit/UIProcess/WebAuthentication/Mock/MockLocalConnection.mm

    r255466 r257085  
    4444}
    4545
    46 void MockLocalConnection::getUserConsent(const String&, UserConsentCallback&& callback) const
    47 {
    48     // Mock async operations.
    49     RunLoop::main().dispatch([configuration = m_configuration, callback = WTFMove(callback)]() mutable {
    50         ASSERT(configuration.local);
    51         if (!configuration.local->acceptAuthentication) {
    52             callback(UserConsent::No);
    53             return;
    54         }
    55         callback(UserConsent::Yes);
    56     });
    57 }
    58 
    5946void MockLocalConnection::getUserConsent(const String&, SecAccessControlRef, UserConsentContextCallback&& callback) const
    6047{
     
    7057}
    7158
    72 void MockLocalConnection::getAttestation(const String& rpId, const String& username, const Vector<uint8_t>& hash, AttestationCallback&& callback) const
     59RetainPtr<SecKeyRef> MockLocalConnection::createCredentialPrivateKey(LAContext *, SecAccessControlRef, const String& secAttrLabel, NSData *secAttrApplicationTag) const
     60{
     61    ASSERT(m_configuration.local);
     62
     63    // Get Key and add it to Keychain.
     64    NSDictionary* options = @{
     65        (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
     66        (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
     67        (id)kSecAttrKeySizeInBits: @256,
     68    };
     69    CFErrorRef errorRef = nullptr;
     70    auto key = adoptCF(SecKeyCreateWithData(
     71        (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:m_configuration.local->privateKeyBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(),
     72        (__bridge CFDictionaryRef)options,
     73        &errorRef
     74    ));
     75    if (errorRef)
     76        return nullptr;
     77
     78    NSDictionary* addQuery = @{
     79        (id)kSecValueRef: (id)key.get(),
     80        (id)kSecClass: (id)kSecClassKey,
     81        (id)kSecAttrLabel: secAttrLabel,
     82        (id)kSecAttrApplicationTag: secAttrApplicationTag,
     83        (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock,
     84#if HAVE(DATA_PROTECTION_KEYCHAIN)
     85        (id)kSecUseDataProtectionKeychain: @YES
     86#else
     87        (id)kSecAttrNoLegacy: @YES
     88#endif
     89    };
     90    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
     91    if (status) {
     92        LOG_ERROR("Couldn't add the key to the keychain. %d", status);
     93        return nullptr;
     94    }
     95
     96    return key;
     97}
     98
     99void MockLocalConnection::getAttestation(SecKeyRef, NSData *, NSData *, AttestationCallback&& callback) const
    73100{
    74101    // Mock async operations.
    75     RunLoop::main().dispatch([configuration = m_configuration, rpId, username, hash, callback = WTFMove(callback)]() mutable {
     102    RunLoop::main().dispatch([configuration = m_configuration, callback = WTFMove(callback)]() mutable {
    76103        ASSERT(configuration.local);
    77104        if (!configuration.local->acceptAttestation) {
    78             callback(NULL, NULL, [NSError errorWithDomain:@"WebAuthentication" code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"The operation couldn't complete." }]);
    79             return;
    80         }
    81 
    82         // Get Key and add it to Keychain.
    83         NSDictionary* options = @{
    84             (id)kSecAttrKeyType: (id)kSecAttrKeyTypeECSECPrimeRandom,
    85             (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
    86             (id)kSecAttrKeySizeInBits: @256,
    87         };
    88         CFErrorRef errorRef = nullptr;
    89         auto key = adoptCF(SecKeyCreateWithData(
    90             (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:configuration.local->privateKeyBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(),
    91             (__bridge CFDictionaryRef)options,
    92             &errorRef
    93         ));
    94         if (errorRef) {
    95             callback(NULL, NULL, (NSError *)errorRef);
    96             return;
    97         }
    98 
    99         // Mock what DeviceIdentity would do.
    100         String label = makeString(username, "@", rpId, "-rk-ucrt");
    101         NSDictionary* addQuery = @{
    102             (id)kSecValueRef: (id)key.get(),
    103             (id)kSecClass: (id)kSecClassKey,
    104             (id)kSecAttrLabel: (id)label,
    105             (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock,
    106 #if HAVE(DATA_PROTECTION_KEYCHAIN)
    107             (id)kSecUseDataProtectionKeychain: @YES
    108 #else
    109             (id)kSecAttrNoLegacy: @YES
    110 #endif
    111         };
    112         OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
    113         if (status) {
    114             callback(NULL, NULL, [NSError errorWithDomain:@"WebAuthentication" code:status userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Couldn't add the key to the keychain. %d", status] }]);
     105            callback(NULL, [NSError errorWithDomain:@"WebAuthentication" code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"The operation couldn't complete." }]);
    115106            return;
    116107        }
     
    118109        auto attestationCertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:configuration.local->userCertificateBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get()));
    119110        auto attestationIssuingCACertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:configuration.local->intermediateCACertificateBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get()));
    120 
    121         callback(key.get(), [NSArray arrayWithObjects: (__bridge id)attestationCertificate.get(), (__bridge id)attestationIssuingCACertificate.get(), nil], NULL);
     111        callback([NSArray arrayWithObjects: (__bridge id)attestationCertificate.get(), (__bridge id)attestationIssuingCACertificate.get(), nil], NULL);
    122112    });
    123113}
  • trunk/Source/WebKit/WebKit.xcodeproj/project.pbxproj

    r256934 r257085  
    11241124                57DCED702142EE680016B847 /* WebAuthenticatorCoordinatorProxyMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57DCED6C2142EAF90016B847 /* WebAuthenticatorCoordinatorProxyMessageReceiver.cpp */; };
    11251125                57DCED712142EE6C0016B847 /* WebAuthenticatorCoordinatorProxyMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 57DCED6D2142EAFA0016B847 /* WebAuthenticatorCoordinatorProxyMessages.h */; };
    1126                 57DCEDAB214C60090016B847 /* DeviceIdentitySPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 57DCEDAA214B9B430016B847 /* DeviceIdentitySPI.h */; };
    11271126                57DCEDAC214C60270016B847 /* LocalAuthenticator.h in Headers */ = {isa = PBXBuildFile; fileRef = 57DCEDA12149C1E20016B847 /* LocalAuthenticator.h */; };
    11281127                57DCEDAD214C602C0016B847 /* LocalConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 57DCEDA7214A568B0016B847 /* LocalConnection.h */; };
     
    38693868                57DCEDA7214A568B0016B847 /* LocalConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalConnection.h; sourceTree = "<group>"; };
    38703869                57DCEDA8214A568B0016B847 /* LocalConnection.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = LocalConnection.mm; sourceTree = "<group>"; };
    3871                 57DCEDAA214B9B430016B847 /* DeviceIdentitySPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeviceIdentitySPI.h; sourceTree = "<group>"; };
    38723870                57DCEDC1214F114C0016B847 /* MockLocalService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockLocalService.h; sourceTree = "<group>"; };
    38733871                57DCEDC2214F114C0016B847 /* MockLocalService.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MockLocalService.mm; sourceTree = "<group>"; };
     
    68156813                                1A5705101BE410E500874AF1 /* BlockSPI.h */,
    68166814                                37C21CAD1E994C0C0029D5F9 /* CorePredictionSPI.h */,
    6817                                 57DCEDAA214B9B430016B847 /* DeviceIdentitySPI.h */,
    68186815                                2DAADA8E2298C21000E36B0C /* DeviceManagementSPI.h */,
    68196816                                57B826402304EB3E00B72EB0 /* NearFieldSPI.h */,
     
    1024610243                                99036AE923A970870000B06A /* DebuggableInfoData.h in Headers */,
    1024710244                                BC032DA610F437D10058C15A /* Decoder.h in Headers */,
    10248                                 57DCEDAB214C60090016B847 /* DeviceIdentitySPI.h in Headers */,
    1024910245                                07297F9F1C17BBEA015F0735 /* DeviceIdHashSaltStorage.h in Headers */,
    1025010246                                2D0C56FD229F1DEA00BD33E7 /* DeviceManagementSoftLink.h in Headers */,
Note: See TracChangeset for help on using the changeset viewer.