Changeset 230012 in webkit
- Timestamp:
- Mar 27, 2018 3:42:45 PM (6 years ago)
- Location:
- trunk
- Files:
-
- 15 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r230009 r230012 1 2018-03-27 Jiewen Tan <jiewen_tan@apple.com> 2 3 [WebAuthN] Implement authenticatorGetAssertion 4 https://bugs.webkit.org/show_bug.cgi?id=183881 5 <rdar://problem/37258628> 6 7 Reviewed by Brent Fulgham. 8 9 This patch does the following few things: 10 1) It implements the spec: https://www.w3.org/TR/webauthn/#op-get-assertion as of 5 December 2017. 11 2) It tweaks encoding/decoding of PublicKeyCredentialRequestOptions such that options can be passed 12 between UI and Web processes. 13 3) It refines the way how LocalAuthenticator::makeCredential find intersection between 14 excludeCredentialDescriptorList and existing credentials in the authenticator, such that it is faster. 15 Basically, it takes the CredentialID from the list and treat it as an ASCII string and put it into a 16 HashSet<String>. It should not matter if a duplicated CredentialID is added. If the hash set is not 17 empty, the algorithm then queries Keychain for all CredentialIDs related to the current RP ID once. 18 For every queried CredentialID, the algorithm then treats it as an ASCII string as well and look for 19 a match in the hash set to produce the intersetction. The new way is also employed in 20 LocalAuthenticator::getAssertion as well. 21 4) It abstracts the way to produce authData and thus reorders a bit of code in 22 LocalAuthenticator::makeCredential. 23 24 Covered by API tests. 25 26 * Modules/webauthn/AuthenticatorManager.cpp: 27 (WebCore::AuthenticatorManager::create const): 28 (WebCore::AuthenticatorManager::discoverFromExternalSource const): 29 * Modules/webauthn/PublicKeyCredentialCreationOptions.h: 30 * Modules/webauthn/PublicKeyCredentialRequestOptions.h: 31 (WebCore::PublicKeyCredentialRequestOptions::encode const): 32 (WebCore::PublicKeyCredentialRequestOptions::decode): 33 * Modules/webauthn/cocoa/LocalAuthenticator.h: 34 * Modules/webauthn/cocoa/LocalAuthenticator.mm: 35 (WebCore::LocalAuthenticatorInternal::buildAuthData): 36 (WebCore::LocalAuthenticatorInternal::produceHashSet): 37 (WebCore::LocalAuthenticator::makeCredential): 38 (WebCore::LocalAuthenticator::getAssertion): 39 (WebCore::LocalAuthenticator::issueClientCertificate const): 40 * WebCore.xcodeproj/project.pbxproj: 41 1 42 2018-03-27 Chris Dumez <cdumez@apple.com> 2 43 -
trunk/Source/WebCore/Modules/webauthn/AuthenticatorManager.cpp
r228572 r230012 161 161 // Only platform attachments will be supported at this stage. Assuming one authenticator per device. 162 162 // Also, resident keys, user verifications and direct attestation are enforced at this tage. 163 // For better performance, no filtering is done here regarding to options.excludeCredentials.163 // For better performance, transports of options.excludeCredentials are checked in LocalAuthenticator. 164 164 if (!m_messenger) { 165 165 promise.reject(Exception { UnknownError, ASCIILiteral("Unknown internal error.") }); … … 220 220 // Only platform attachments will be supported at this stage. Assuming one authenticator per device. 221 221 // Also, resident keys, user verifications and direct attestation are enforced at this tage. 222 // For better performance, no filtering is done here regarding to options.allowCredentials.222 // For better performance, filtering of options.allowCredentials is done in LocalAuthenticator. 223 223 if (!m_messenger) { 224 224 promise.reject(Exception { UnknownError, ASCIILiteral("Unknown internal error.") }); -
trunk/Source/WebCore/Modules/webauthn/PublicKeyCredentialCreationOptions.h
r229699 r230012 48 48 struct UserEntity : public Entity { 49 49 BufferSource id; // id becomes idVector once it is passed to UIProcess. 50 mutableVector<uint8_t> idVector;50 Vector<uint8_t> idVector; 51 51 String displayName; 52 52 }; … … 141 141 result.user.icon = source.user.icon.isolatedCopy(); 142 142 result.user.displayName = source.user.displayName.isolatedCopy(); 143 result.user.idVector = WTFMove(source.user.idVector);143 result.user.idVector = source.user.idVector; 144 144 return result; 145 145 } -
trunk/Source/WebCore/Modules/webauthn/PublicKeyCredentialRequestOptions.h
r227764 r230012 39 39 mutable String rpId; 40 40 Vector<PublicKeyCredentialDescriptor> allowCredentials; 41 42 template<class Encoder> void encode(Encoder&) const; 43 template<class Decoder> static std::optional<PublicKeyCredentialRequestOptions> decode(Decoder&); 41 44 }; 45 46 // Not every member is encoded. 47 template<class Encoder> 48 void PublicKeyCredentialRequestOptions::encode(Encoder& encoder) const 49 { 50 encoder << rpId << allowCredentials; 51 } 52 53 template<class Decoder> 54 std::optional<PublicKeyCredentialRequestOptions> PublicKeyCredentialRequestOptions::decode(Decoder& decoder) 55 { 56 PublicKeyCredentialRequestOptions result; 57 if (!decoder.decode(result.rpId)) 58 return std::nullopt; 59 if (!decoder.decode(result.allowCredentials)) 60 return std::nullopt; 61 return result; 62 } 42 63 43 64 } // namespace WebCore -
trunk/Source/WebCore/Modules/webauthn/cocoa/LocalAuthenticator.h
r229699 r230012 36 36 struct ExceptionData; 37 37 struct PublicKeyCredentialCreationOptions; 38 struct PublicKeyCredentialRequestOptions; 38 39 39 40 using CreationCallback = Function<void(const Vector<uint8_t>&, const Vector<uint8_t>&)>; 41 using RequestCallback = Function<void(const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&)>; 40 42 using ExceptionCallback = Function<void(const WebCore::ExceptionData&)>; 41 43 … … 50 52 51 53 void makeCredential(const Vector<uint8_t>& hash, const PublicKeyCredentialCreationOptions&, CreationCallback&&, ExceptionCallback&&); 54 void getAssertion(const Vector<uint8_t>& hash, const PublicKeyCredentialRequestOptions&, RequestCallback&&, ExceptionCallback&&); 52 55 bool isAvailable() const; 53 56 -
trunk/Source/WebCore/Modules/webauthn/cocoa/LocalAuthenticator.mm
r229699 r230012 33 33 #import "ExceptionData.h" 34 34 #import "PublicKeyCredentialCreationOptions.h" 35 #import "PublicKeyCredentialRequestOptions.h" 35 36 #import <Security/SecItem.h> 36 37 #import <pal/crypto/CryptoDigest.h> 37 38 #import <pal/spi/cocoa/DeviceIdentitySPI.h> 38 39 #import <wtf/BlockPtr.h> 40 #import <wtf/HashSet.h> 39 41 #import <wtf/MainThread.h> 40 42 #import <wtf/RetainPtr.h> 41 43 #import <wtf/Vector.h> 44 #import <wtf/text/StringHash.h> 42 45 43 46 #import "LocalAuthenticationSoftLink.h" … … 47 50 namespace LocalAuthenticatorInternal { 48 51 49 // UP, UV and AT are set. See https://www.w3.org/TR/webauthn/#flags. 50 const uint8_t authenticatorDataFlags = 69; 52 // See https://www.w3.org/TR/webauthn/#flags. 53 const uint8_t makeCredentialFlags = 0b01000101; // UP, UV and AT are set. 54 const uint8_t getAssertionFlags = 0b00000101; // UP and UV are set. 51 55 // FIXME(rdar://problem/38320512): Define Apple AAGUID. 52 56 const uint8_t AAGUID[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 16 bytes 53 const size_t maxCredentialIdLength = 0xffff; // 2 bytes 57 // Credential ID is currently SHA-1 of the corresponding public key. 58 // FIXME(183534): Assume little endian here. 59 const union { 60 uint16_t integer; 61 uint8_t bytes[2]; 62 } credentialIdLength = {0x0014}; 54 63 const size_t ES256KeySizeInBytes = 32; 64 const size_t authDataPrefixFixedSize = 37; // hash(32) + flags(1) + counter(4) 65 66 #if PLATFORM(IOS) 67 // https://www.w3.org/TR/webauthn/#sec-authenticator-data 68 static Vector<uint8_t> buildAuthData(const String& rpId, const uint8_t flags, const uint32_t counter, const Vector<uint8_t>& optionalAttestedCredentialData) 69 { 70 Vector<uint8_t> authData; 71 authData.reserveCapacity(authDataPrefixFixedSize + optionalAttestedCredentialData.size()); 72 73 // RP ID hash 74 auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256); 75 // FIXME(183534): Test IDN. 76 ASSERT(rpId.isAllASCII()); 77 auto asciiRpId = rpId.ascii(); 78 crypto->addBytes(asciiRpId.data(), asciiRpId.length()); 79 authData = crypto->computeHash(); 80 81 // FLAGS 82 authData.append(flags); 83 84 // COUNTER 85 // FIXME(183534): Assume little endian here. 86 union { 87 uint32_t integer; 88 uint8_t bytes[4]; 89 } counterUnion; 90 counterUnion.integer = counter; 91 authData.append(counterUnion.bytes, sizeof(counterUnion.bytes)); 92 93 // ATTESTED CRED. DATA 94 authData.appendVector(optionalAttestedCredentialData); 95 96 return authData; 97 } 98 99 inline HashSet<String> produceHashSet(const Vector<PublicKeyCredentialDescriptor>& credentialDescriptors) 100 { 101 HashSet<String> result; 102 for (auto& credentialDescriptor : credentialDescriptors) { 103 if (credentialDescriptor.transports.isEmpty() && credentialDescriptor.type == PublicKeyCredentialType::PublicKey && credentialDescriptor.idVector.size() == credentialIdLength.integer) 104 result.add(String(reinterpret_cast<const char*>(credentialDescriptor.idVector.data()), credentialDescriptor.idVector.size())); 105 } 106 return result; 107 } 108 #endif // !PLATFORM(IOS) 55 109 56 110 } // LocalAuthenticatorInternal … … 63 117 // FIXME(182772) 64 118 ASSERT_UNUSED(hash, hash == hash); 65 ASSERT_UNUSED(options, options.rp.name.isEmpty());119 ASSERT_UNUSED(options, !options.rp.id.isEmpty()); 66 120 ASSERT_UNUSED(callback, callback); 67 121 exceptionCallback({ NotAllowedError, ASCIILiteral("No avaliable authenticators.") }); … … 85 139 86 140 // Step 3. 87 for (auto& excludeCredential : options.excludeCredentials) { 88 if (excludeCredential.type == PublicKeyCredentialType::PublicKey && excludeCredential.transports.isEmpty()) { 89 // Search Keychain for the Credential ID and RP ID, which is stored in the kSecAttrApplicationLabel and kSecAttrLabel attribute respectively. 90 NSDictionary *query = @{ 91 (id)kSecClass: (id)kSecClassKey, 92 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, 93 (id)kSecAttrApplicationLabel: [NSData dataWithBytes:excludeCredential.idVector.data() length:excludeCredential.idVector.size()], 94 (id)kSecAttrLabel: options.rp.id 95 }; 96 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL); 97 if (!status) { 141 HashSet<String> excludeCredentialIds = produceHashSet(options.excludeCredentials); 142 if (!excludeCredentialIds.isEmpty()) { 143 // Search Keychain for the RP ID. 144 NSDictionary *query = @{ 145 (id)kSecClass: (id)kSecClassKey, 146 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, 147 (id)kSecAttrLabel: options.rp.id, 148 (id)kSecReturnAttributes: @YES, 149 (id)kSecMatchLimit: (id)kSecMatchLimitAll, 150 }; 151 CFTypeRef attributesArrayRef = nullptr; 152 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef); 153 if (status && status != errSecItemNotFound) { 154 LOG_ERROR("Couldn't query Keychain: %d", status); 155 exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") }); 156 return; 157 } 158 auto retainAttributesArray = adoptCF(attributesArrayRef); 159 160 for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) { 161 NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel]; 162 if (excludeCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) { 98 163 exceptionCallback({ NotAllowedError, ASCIILiteral("At least one credential matches an entry of the excludeCredentials list in the platform attached authenticator.") }); 99 return;100 }101 if (status != errSecItemNotFound) {102 LOG_ERROR("Couldn't query Keychain: %d", status);103 exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") });104 164 return; 105 165 } … … 112 172 auto context = adoptNS([allocLAContextInstance() init]); 113 173 NSError *error = nil; 114 NSString *reason = [NSString stringWithFormat:@"Allow %@ to create a public key credential for %@", (id)options.rp.id, (id)options.user.name];115 174 if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) { 116 175 LOG_ERROR("Couldn't evaluate authentication with biometrics policy: %@", error); … … 120 179 } 121 180 181 NSString *reason = [NSString stringWithFormat:@"Allow %@ to create a public key credential for %@", (id)options.rp.id, (id)options.user.name]; 122 182 // FIXME(183534): Optimize the following nested callbacks and threading. 123 183 [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:reason reply:BlockPtr<void(BOOL, NSError *)>::fromCallable([weakThis = m_weakFactory.createWeakPtr(*this), callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), options = crossThreadCopy(options), hash] (BOOL success, NSError *error) mutable { … … 158 218 ASSERT(certificates && ([certificates count] == 2)); 159 219 160 // Step 7.2 -7.4.220 // Step 7.2-7.4. 161 221 // FIXME(183533): A single kSecClassKey item couldn't store all meta data. The following schema is a tentative solution 162 222 // to accommodate the most important meta data, i.e. RP ID, Credential ID, and userhandle. … … 181 241 (id)kSecReturnAttributes: @YES 182 242 }; 183 CFTypeRef attributesRef = NULL;243 CFTypeRef attributesRef = nullptr; 184 244 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)credentialIdQuery, &attributesRef); 185 245 if (status) { … … 192 252 NSDictionary *nsAttributes = (NSDictionary *)attributesRef; 193 253 NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel]; 194 credentialId.append( static_cast<const uint8_t*>(nsCredentialId.bytes), nsCredentialId.length);254 credentialId.append(reinterpret_cast<const uint8_t*>(nsCredentialId.bytes), nsCredentialId.length); 195 255 196 256 NSDictionary *updateQuery = @{ … … 211 271 } 212 272 213 // Step 12. 214 // Apple Attestation Cont' 215 // Assemble the attestation object: 216 // https://www.w3.org/TR/webauthn/#attestation-object 217 // FIXME(183534): authData could throttle. 218 Vector<uint8_t> authData; 273 // Step 10. 274 // FIXME(183533): store the counter. 275 uint32_t counter = 0; 276 277 // FIXME(183534): attestedCredentialData could throttle. 278 // Step 11. https://www.w3.org/TR/webauthn/#attested-credential-data 279 Vector<uint8_t> attestedCredentialData; 219 280 { 220 // RP ID hash 221 auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256); 222 // FIXME(183534): Test IDN. 223 ASSERT(options.rp.id.isAllASCII()); 224 auto asciiRpId = options.rp.id.ascii(); 225 crypto->addBytes(asciiRpId.data(), asciiRpId.length()); 226 authData = crypto->computeHash(); 227 228 // FLAGS 229 authData.append(authenticatorDataFlags); 230 231 // Step. 10. 232 // COUNTER 233 // FIXME(183533): store the counter. 234 union { 235 uint32_t integer; 236 uint8_t bytes[4]; 237 } counter = {0x00000000}; 238 authData.append(counter.bytes, sizeof(counter.bytes)); 239 240 // Step 11. 241 // AAGUID 242 authData.append(AAGUID, sizeof(AAGUID)); 243 244 // L 245 ASSERT(credentialId.size() <= maxCredentialIdLength); 246 // Assume little endian here. 247 union { 248 uint16_t integer; 249 uint8_t bytes[2]; 250 } credentialIdLength; 251 credentialIdLength.integer = static_cast<uint16_t>(credentialId.size()); 252 authData.append(credentialIdLength.bytes, sizeof(uint16_t)); 253 254 // CREDENTIAL ID 255 authData.appendVector(credentialId); 256 257 // CREDENTIAL PUBLIC KEY 258 CFDataRef publicKeyDataRef = NULL; 281 // aaguid 282 attestedCredentialData.append(AAGUID, sizeof(AAGUID)); 283 284 // credentialIdLength 285 ASSERT(credentialId.size() == credentialIdLength.integer); 286 // FIXME(183534): Assume little endian here. 287 attestedCredentialData.append(credentialIdLength.bytes, sizeof(uint16_t)); 288 289 // credentialId 290 attestedCredentialData.appendVector(credentialId); 291 292 // credentialPublicKey 293 CFDataRef publicKeyDataRef = nullptr; 259 294 { 260 295 auto publicKey = adoptCF(SecKeyCopyPublicKey(privateKey)); 261 CFErrorRef errorRef = NULL;296 CFErrorRef errorRef = nullptr; 262 297 publicKeyDataRef = SecKeyCopyExternalRepresentation(publicKey.get(), &errorRef); 263 298 auto retainError = adoptCF(errorRef); … … 289 324 return; 290 325 } 291 authData.appendVector(cosePublicKey.value()); 292 } 293 326 attestedCredentialData.appendVector(cosePublicKey.value()); 327 } 328 329 // Step 12. 330 auto authData = buildAuthData(options.rp.id, makeCredentialFlags, counter, attestedCredentialData); 331 332 // Step 13. Apple Attestation Cont' 333 // Assemble the attestation object: 334 // https://www.w3.org/TR/webauthn/#attestation-object 294 335 cbor::CBORValue::MapValue attestationStatementMap; 295 336 { 296 337 Vector<uint8_t> signature; 297 338 { 298 CFErrorRef errorRef = NULL;339 CFErrorRef errorRef = nullptr; 299 340 // FIXME(183652): Reduce prompt for biometrics 300 341 CFDataRef signatureRef = SecKeyCreateSignature(privateKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)[NSData dataWithBytes:authData.data() length:authData.size()], &errorRef); 301 342 auto retainError = adoptCF(errorRef); 302 343 if (errorRef) { 303 LOG_ERROR("Couldn't export the public key: %@", (NSError*)errorRef);344 LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef); 304 345 exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") }); 305 346 return; … … 307 348 auto retainSignature = adoptCF(signatureRef); 308 349 NSData *nsSignature = (NSData *)signatureRef; 309 signature.append( static_cast<const uint8_t*>(nsSignature.bytes), nsSignature.length);350 signature.append(reinterpret_cast<const uint8_t*>(nsSignature.bytes), nsSignature.length); 310 351 } 311 352 attestationStatementMap[cbor::CBORValue("alg")] = cbor::CBORValue(COSE::ES256); … … 317 358 NSData *nsData = (NSData *)dataRef; 318 359 Vector<uint8_t> data; 319 data.append( static_cast<const uint8_t*>(nsData.bytes), nsData.length);360 data.append(reinterpret_cast<const uint8_t*>(nsData.bytes), nsData.length); 320 361 cborArray.append(cbor::CBORValue(WTFMove(data))); 321 362 } … … 337 378 }).get()); 338 379 }).get()]; 380 #endif // !PLATFORM(IOS) 381 } 382 383 void LocalAuthenticator::getAssertion(const Vector<uint8_t>& hash, const PublicKeyCredentialRequestOptions& options, RequestCallback&& callback, ExceptionCallback&& exceptionCallback) 384 { 385 using namespace LocalAuthenticatorInternal; 386 387 #if !PLATFORM(IOS) 388 // FIXME(182772) 389 ASSERT_UNUSED(hash, hash == hash); 390 ASSERT_UNUSED(options, !options.rpId.isEmpty()); 391 ASSERT_UNUSED(callback, callback); 392 exceptionCallback({ NotAllowedError, ASCIILiteral("No avaliable authenticators.") }); 393 #else 394 // The following implements https://www.w3.org/TR/webauthn/#op-get-assertion as of 5 December 2017. 395 // Skip Step 2 as requireUserVerification is enforced. 396 // Skip Step 8 as extensions are not supported yet. 397 // Step 12 is implicitly captured by all UnknownError exception callbacks. 398 // 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. 399 HashSet<String> allowCredentialIds = produceHashSet(options.allowCredentials); 400 if (!options.allowCredentials.isEmpty() && allowCredentialIds.isEmpty()) { 401 exceptionCallback({ NotAllowedError, ASCIILiteral("No matched credentials are found in the platform attached authenticator.") }); 402 return; 403 } 404 405 // Search Keychain for the RP ID. 406 NSDictionary *query = @{ 407 (id)kSecClass: (id)kSecClassKey, 408 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, 409 (id)kSecAttrLabel: options.rpId, 410 (id)kSecReturnAttributes: @YES, 411 (id)kSecMatchLimit: (id)kSecMatchLimitAll, 412 }; 413 CFTypeRef attributesArrayRef = nullptr; 414 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesArrayRef); 415 if (status && status != errSecItemNotFound) { 416 LOG_ERROR("Couldn't query Keychain: %d", status); 417 exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") }); 418 return; 419 } 420 auto retainAttributesArray = adoptCF(attributesArrayRef); 421 422 NSArray *intersectedCredentialsAttributes = nil; 423 if (options.allowCredentials.isEmpty()) 424 intersectedCredentialsAttributes = (NSArray *)attributesArrayRef; 425 else { 426 NSMutableArray *result = [NSMutableArray arrayWithCapacity:allowCredentialIds.size()]; 427 for (NSDictionary *nsAttributes in (NSArray *)attributesArrayRef) { 428 NSData *nsCredentialId = nsAttributes[(id)kSecAttrApplicationLabel]; 429 if (allowCredentialIds.contains(String(reinterpret_cast<const char*>(nsCredentialId.bytes), nsCredentialId.length))) 430 [result addObject:nsAttributes]; 431 } 432 intersectedCredentialsAttributes = result; 433 } 434 if (!intersectedCredentialsAttributes.count) { 435 exceptionCallback({ NotAllowedError, ASCIILiteral("No matched credentials are found in the platform attached authenticator.") }); 436 return; 437 } 438 439 // Step 6. 440 // FIXME(rdar://problem/35900534): We don't have an UI to prompt users for selecting intersectedCredentials, and therefore we always use the first one for now. 441 NSDictionary *selectedCredentialAttributes = intersectedCredentialsAttributes[0]; 442 443 // Step 7. Get user consent. 444 // FIXME(183534): The lifetime of context is managed by reply and the early return, which is a bit subtle. 445 LAContext *context = [allocLAContextInstance() init]; 446 NSError *error = nil; 447 if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) { 448 auto retainContext = adoptNS(context); 449 LOG_ERROR("Couldn't evaluate authentication with biometrics policy: %@", error); 450 // FIXME(182767) 451 exceptionCallback({ NotAllowedError, ASCIILiteral("No avaliable authenticators.") }); 452 return; 453 } 454 455 Vector<uint8_t> credentialId; 456 NSData *nsCredentialId = selectedCredentialAttributes[(id)kSecAttrApplicationLabel]; 457 credentialId.append(reinterpret_cast<const uint8_t*>(nsCredentialId.bytes), nsCredentialId.length); 458 Vector<uint8_t> userhandle; 459 NSData *nsUserhandle = selectedCredentialAttributes[(id)kSecAttrApplicationTag]; 460 userhandle.append(reinterpret_cast<const uint8_t*>(nsUserhandle.bytes), nsUserhandle.length); 461 auto reply = BlockPtr<void(BOOL, NSError *)>::fromCallable([callback = WTFMove(callback), exceptionCallback = WTFMove(exceptionCallback), rpId = options.rpId.isolatedCopy(), hash, credentialId = WTFMove(credentialId), userhandle = WTFMove(userhandle), context = adoptNS(context)] (BOOL success, NSError *error) mutable { 462 ASSERT(!isMainThread()); 463 if (!success || error) { 464 LOG_ERROR("Couldn't authenticate with biometrics: %@", error); 465 exceptionCallback({ NotAllowedError, ASCIILiteral("Couldn't get user consent.") }); 466 return; 467 } 468 469 // Step 9-10. 470 // FIXME(183533): Due to the stated Keychain limitations, we can't save the counter value. 471 // Therefore, it is always zero. 472 uint32_t counter = 0; 473 auto authData = buildAuthData(rpId, getAssertionFlags, counter, { }); 474 475 // Step 11. 476 Vector<uint8_t> signature; 477 { 478 NSDictionary *query = @{ 479 (id)kSecClass: (id)kSecClassKey, 480 (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate, 481 (id)kSecAttrApplicationLabel: [NSData dataWithBytes:credentialId.data() length:credentialId.size()], 482 (id)kSecUseAuthenticationContext: context.get(), 483 (id)kSecReturnRef: @YES, 484 }; 485 CFTypeRef privateKeyRef = nullptr; 486 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &privateKeyRef); 487 if (status) { 488 LOG_ERROR("Couldn't get the private key reference: %d", status); 489 exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") }); 490 return; 491 } 492 auto privateKey = adoptCF(privateKeyRef); 493 494 NSMutableData *dataToSign = [NSMutableData dataWithBytes:authData.data() length:authData.size()]; 495 [dataToSign appendBytes:hash.data() length:hash.size()]; 496 497 CFErrorRef errorRef = nullptr; 498 // FIXME: Converting CFTypeRef to SecKeyRef is quite subtle here. 499 CFDataRef signatureRef = SecKeyCreateSignature((__bridge SecKeyRef)((id)privateKeyRef), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)dataToSign, &errorRef); 500 auto retainError = adoptCF(errorRef); 501 if (errorRef) { 502 LOG_ERROR("Couldn't generate the signature: %@", (NSError*)errorRef); 503 exceptionCallback({ UnknownError, ASCIILiteral("Unknown internal error.") }); 504 return; 505 } 506 auto retainSignature = adoptCF(signatureRef); 507 NSData *nsSignature = (NSData *)signatureRef; 508 signature.append(reinterpret_cast<const uint8_t*>(nsSignature.bytes), nsSignature.length); 509 } 510 511 // Step 13. 512 callback(credentialId, authData, signature, userhandle); 513 }); 514 515 // FIXME(183533): Use userhandle instead of username due to the stated Keychain limitations. 516 NSString *reason = [NSString stringWithFormat:@"Log into %@ with %@.", (id)options.rpId, selectedCredentialAttributes[(id)kSecAttrApplicationTag]]; 517 [context evaluateAccessControl:(__bridge SecAccessControlRef)selectedCredentialAttributes[(id)kSecAttrAccessControl] operation:LAAccessControlOperationUseKeySign localizedReason:reason reply:reply.get()]; 339 518 #endif // !PLATFORM(IOS) 340 519 } … … 372 551 SecAccessControlRef accessControlRef; 373 552 { 374 CFErrorRef errorRef = NULL;553 CFErrorRef errorRef = nullptr; 375 554 accessControlRef = SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence, &errorRef); 376 555 auto retainError = adoptCF(errorRef); -
trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj
r229967 r230012 1710 1710 57303BF42009904600355965 /* JSPublicKeyCredentialDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303BF22009904500355965 /* JSPublicKeyCredentialDescriptor.h */; }; 1711 1711 57303C002009951C00355965 /* JSPublicKeyCredentialType.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303BF92009913400355965 /* JSPublicKeyCredentialType.h */; }; 1712 57303C0A20099BAD00355965 /* PublicKeyCredentialRequestOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C06200998F800355965 /* PublicKeyCredentialRequestOptions.h */; };1712 57303C0A20099BAD00355965 /* PublicKeyCredentialRequestOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C06200998F800355965 /* PublicKeyCredentialRequestOptions.h */; settings = {ATTRIBUTES = (Private, ); }; }; 1713 1713 57303C1120099CB100355965 /* JSPublicKeyCredentialRequestOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C0C20099C7500355965 /* JSPublicKeyCredentialRequestOptions.h */; }; 1714 1714 57303C192009A2F300355965 /* JSPublicKeyCredentialCreationOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 57303C132009A25700355965 /* JSPublicKeyCredentialCreationOptions.h */; }; -
trunk/Source/WebKit/ChangeLog
r230009 r230012 1 2018-03-27 Jiewen Tan <jiewen_tan@apple.com> 2 3 [WebAuthN] Implement authenticatorGetAssertion 4 https://bugs.webkit.org/show_bug.cgi?id=183881 5 <rdar://problem/37258628> 6 7 Reviewed by Brent Fulgham. 8 9 * Shared/WebPreferences.yaml: 10 * UIProcess/CredentialManagement/WebCredentialsMessengerProxy.cpp: 11 (WebKit::WebCredentialsMessengerProxy::makeCredential): 12 (WebKit::WebCredentialsMessengerProxy::getAssertion): 13 (WebKit::WebCredentialsMessengerProxy::getAssertionReply): 14 * UIProcess/CredentialManagement/WebCredentialsMessengerProxy.h: 15 * UIProcess/CredentialManagement/WebCredentialsMessengerProxy.messages.in: 16 * WebProcess/CredentialManagement/WebCredentialsMessenger.cpp: 17 (WebKit::WebCredentialsMessenger::getAssertion): 18 (WebKit::WebCredentialsMessenger::getAssertionReply): 19 * WebProcess/CredentialManagement/WebCredentialsMessenger.messages.in: 20 1 21 2018-03-27 Chris Dumez <cdumez@apple.com> 2 22 -
trunk/Source/WebKit/UIProcess/CredentialManagement/WebCredentialsMessengerProxy.cpp
r229699 r230012 58 58 } 59 59 // FIXME(183534): Weak pointers doesn't work in another thread because of race condition. 60 // FIXME(183534): Unify callbacks. 60 61 auto weakThis = m_weakFactory.createWeakPtr(*this); 61 62 auto callback = [weakThis, messageId] (const Vector<uint8_t>& credentialId, const Vector<uint8_t>& attestationObject) { … … 72 73 } 73 74 74 void WebCredentialsMessengerProxy::getAssertion(uint64_t )75 void WebCredentialsMessengerProxy::getAssertion(uint64_t messageId, const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialRequestOptions& options) 75 76 { 77 // FIXME(182767) 78 if (!m_authenticator) 79 exceptionReply(messageId, { WebCore::NotAllowedError, ASCIILiteral("No avaliable authenticators.") }); 80 // FIXME(183534): Weak pointers doesn't work in another thread because of race condition. 81 // FIXME(183534): Unify callbacks. 82 auto weakThis = m_weakFactory.createWeakPtr(*this); 83 auto callback = [weakThis, messageId] (const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle) { 84 if (weakThis) 85 weakThis->getAssertionReply(messageId, credentialId, authenticatorData, signature, userHandle); 86 }; 87 auto exceptionCallback = [weakThis, messageId] (const WebCore::ExceptionData& exception) { 88 if (weakThis) 89 weakThis->exceptionReply(messageId, exception); 90 }; 91 m_authenticator->getAssertion(hash, options, WTFMove(callback), WTFMove(exceptionCallback)); 76 92 } 77 93 … … 95 111 } 96 112 113 void WebCredentialsMessengerProxy::getAssertionReply(uint64_t messageId, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle) 114 { 115 m_webPageProxy.send(Messages::WebCredentialsMessenger::GetAssertionReply(messageId, credentialId, authenticatorData, signature, userHandle)); 116 } 117 97 118 void WebCredentialsMessengerProxy::isUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool result) 98 119 { -
trunk/Source/WebKit/UIProcess/CredentialManagement/WebCredentialsMessengerProxy.h
r229699 r230012 38 38 struct ExceptionData; 39 39 struct PublicKeyCredentialCreationOptions; 40 struct PublicKeyCredentialRequestOptions; 40 41 } 41 42 … … 56 57 // Receivers. 57 58 void makeCredential(uint64_t messageId, const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialCreationOptions&); 58 void getAssertion(uint64_t messageId );59 void getAssertion(uint64_t messageId, const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialRequestOptions&); 59 60 void isUserVerifyingPlatformAuthenticatorAvailable(uint64_t messageId); 60 61 … … 62 63 void exceptionReply(uint64_t messageId, const WebCore::ExceptionData&); 63 64 void makeCredentialReply(uint64_t messageId, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& attestationObject); 65 void getAssertionReply(uint64_t messageId, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle); 64 66 void isUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool); 65 67 -
trunk/Source/WebKit/UIProcess/CredentialManagement/WebCredentialsMessengerProxy.messages.in
r229699 r230012 28 28 29 29 MakeCredential(uint64_t messageId, Vector<uint8_t> hash, struct WebCore::PublicKeyCredentialCreationOptions options); 30 GetAssertion(uint64_t messageId );30 GetAssertion(uint64_t messageId, Vector<uint8_t> hash, struct WebCore::PublicKeyCredentialRequestOptions options); 31 31 IsUserVerifyingPlatformAuthenticatorAvailable(uint64_t messageId); 32 32 } -
trunk/Source/WebKit/WebProcess/CredentialManagement/WebCredentialsMessenger.cpp
r229699 r230012 34 34 #include "WebProcess.h" 35 35 #include <WebCore/PublicKeyCredentialCreationOptions.h> 36 #include <WebCore/PublicKeyCredentialRequestOptions.h> 36 37 37 38 namespace WebKit { … … 54 55 } 55 56 56 void WebCredentialsMessenger::getAssertion(const Vector<uint8_t>& , const WebCore::PublicKeyCredentialRequestOptions&, WebCore::RequestCompletionHandler&&)57 void WebCredentialsMessenger::getAssertion(const Vector<uint8_t>& hash, const WebCore::PublicKeyCredentialRequestOptions& options, WebCore::RequestCompletionHandler&& handler) 57 58 { 59 auto messageId = addRequestCompletionHandler(WTFMove(handler)); 60 m_webPage.send(Messages::WebCredentialsMessengerProxy::GetAssertion(messageId, hash, options)); 58 61 } 59 62 … … 72 75 void WebCredentialsMessenger::getAssertionReply(uint64_t messageId, const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authenticatorData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userHandle) 73 76 { 77 auto handler = takeRequestCompletionHandler(messageId); 78 handler(WebCore::AssertionReturnBundle(ArrayBuffer::create(credentialId.data(), credentialId.size()), ArrayBuffer::create(authenticatorData.data(), authenticatorData.size()), ArrayBuffer::create(signature.data(), signature.size()), ArrayBuffer::create(userHandle.data(), userHandle.size()))); 74 79 } 75 80 -
trunk/Source/WebKit/WebProcess/CredentialManagement/WebCredentialsMessenger.messages.in
r229699 r230012 31 31 GetAssertionReply(uint64_t messageId, Vector<uint8_t> credentialId, Vector<uint8_t> authenticatorData, Vector<uint8_t> signature, Vector<uint8_t> userHandle); 32 32 IsUserVerifyingPlatformAuthenticatorAvailableReply(uint64_t messageId, bool result); 33 exceptionReply(uint64_t messageId, struct WebCore::ExceptionData exception);34 33 } 35 34 -
trunk/Tools/ChangeLog
r230011 r230012 1 2018-03-27 Jiewen Tan <jiewen_tan@apple.com> 2 3 [WebAuthN] Implement authenticatorGetAssertion 4 https://bugs.webkit.org/show_bug.cgi?id=183881 5 <rdar://problem/37258628> 6 7 Reviewed by Brent Fulgham. 8 9 * TestWebKitAPI/Tests/ios/LocalAuthenticator.mm: 10 (TestWebKitAPI::getTestKey): 11 (TestWebKitAPI::addTestKeyToKeychain): 12 (TestWebKitAPI::LAEvaluatePolicyFailedSwizzler::evaluatePolicyFailed): 13 (TestWebKitAPI::LAEvaluatePolicyPassedSwizzler::evaluatePolicyPassed): 14 (TestWebKitAPI::LAEvaluateAccessControlFailedSwizzler::LAEvaluateAccessControlFailedSwizzler): 15 (TestWebKitAPI::LAEvaluateAccessControlFailedSwizzler::evaluateAccessControlFailed): 16 (TestWebKitAPI::LAEvaluateAccessControlPassedSwizzler::LAEvaluateAccessControlPassedSwizzler): 17 (TestWebKitAPI::LAEvaluateAccessControlPassedSwizzler::evaluateAccessControlPassed): 18 (TestWebKitAPI::TEST): 19 1 20 2018-03-27 Brian Burg <bburg@apple.com> 2 21 -
trunk/Tools/TestWebKitAPI/Tests/ios/LocalAuthenticator.mm
r229727 r230012 39 39 #import <WebCore/LocalAuthenticator.h> 40 40 #import <WebCore/PublicKeyCredentialCreationOptions.h> 41 #import <WebCore/PublicKeyCredentialRequestOptions.h> 41 42 #import <wtf/BlockPtr.h> 42 43 #import <wtf/text/Base64.h> … … 86 87 (id)kSecAttrKeySizeInBits: @256, 87 88 }; 88 CFErrorRef errorRef = NULL;89 CFErrorRef errorRef = nullptr; 89 90 auto key = adoptCF(SecKeyCreateWithData( 90 91 (__bridge CFDataRef)adoptNS([[NSData alloc] initWithBase64EncodedString:testES256PrivateKeyBase64 options:NSDataBase64DecodingIgnoreUnknownCharacters]).get(), … … 97 98 } 98 99 100 void addTestKeyToKeychain() 101 { 102 auto key = getTestKey(); 103 NSDictionary* addQuery = @{ 104 (id)kSecValueRef: (id)key.get(), 105 (id)kSecClass: (id)kSecClassKey, 106 (id)kSecAttrLabel: testRpId, 107 (id)kSecAttrApplicationTag: [NSData dataWithBytes:testUserhandle length:sizeof(testUserhandle)], 108 }; 109 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL); 110 EXPECT_FALSE(status); 111 } 112 99 113 void cleanUpKeychain() 100 114 { … … 142 156 } 143 157 private: 144 static void evaluatePolicyFailed(id self, SEL _cmd, LAPolicy policy, NSString * reason, void (^reply)(BOOL success, NSError *error))158 static void evaluatePolicyFailed(id, SEL, LAPolicy, NSString *, void (^reply)(BOOL, NSError *)) 145 159 { 146 160 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ … … 159 173 } 160 174 private: 161 static void evaluatePolicyPassed(id self, SEL _cmd, LAPolicy policy, NSString * reason, void (^reply)(BOOL success, NSError *error)) 175 static void evaluatePolicyPassed(id, SEL, LAPolicy, NSString *, void (^reply)(BOOL, NSError *)) 176 { 177 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 178 Util::sleep(1); // mimic user interaction delay 179 reply(YES, nil); 180 }); 181 } 182 InstanceMethodSwizzler m_swizzler; 183 }; 184 185 class LAEvaluateAccessControlFailedSwizzler { 186 public: 187 LAEvaluateAccessControlFailedSwizzler() 188 : m_swizzler([LAContext class], @selector(evaluateAccessControl:operation:localizedReason:reply:), reinterpret_cast<IMP>(evaluateAccessControlFailed)) 189 { 190 } 191 private: 192 static void evaluateAccessControlFailed(id, SEL, SecAccessControlRef, LAAccessControlOperation, NSString *, void (^reply)(BOOL, NSError *)) 193 { 194 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 195 Util::sleep(1); // mimic user interaction delay 196 reply(NO, nil); 197 }); 198 } 199 InstanceMethodSwizzler m_swizzler; 200 }; 201 202 class LAEvaluateAccessControlPassedSwizzler { 203 public: 204 LAEvaluateAccessControlPassedSwizzler() 205 : m_swizzler([LAContext class], @selector(evaluateAccessControl:operation:localizedReason:reply:), reinterpret_cast<IMP>(evaluateAccessControlPassed)) 206 { 207 } 208 private: 209 static void evaluateAccessControlPassed(id, SEL, SecAccessControlRef, LAAccessControlOperation, NSString *, void (^reply)(BOOL, NSError *)) 162 210 { 163 211 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ … … 235 283 TEST(LocalAuthenticator, MakeCredentialExcludeCredentialsMatch) 236 284 { 237 // Insert the test key into Keychain 238 auto key = getTestKey(); 239 NSDictionary* addQuery = @{ 240 (id)kSecValueRef: (id)key.get(), 241 (id)kSecClass: (id)kSecClassKey, 242 (id)kSecAttrLabel: testRpId, 243 (id)kSecAttrApplicationTag: [NSData dataWithBytes:testUserhandle length:sizeof(testUserhandle)], 244 }; 245 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL); 246 EXPECT_FALSE(status); 247 248 // Invoke the LocalAuthenticator. 285 addTestKeyToKeychain(); 286 249 287 WebCore::PublicKeyCredentialDescriptor descriptor; 250 288 descriptor.type = WebCore::PublicKeyCredentialType::PublicKey; … … 351 389 352 390 // Insert the older credential 353 auto key = getTestKey(); 354 NSDictionary* addQuery = @{ 355 (id)kSecValueRef: (id)key.get(), 356 (id)kSecClass: (id)kSecClassKey, 357 (id)kSecAttrLabel: testRpId, 358 (id)kSecAttrApplicationTag: [NSData dataWithBytes:testUserhandle length:sizeof(testUserhandle)], 359 }; 360 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL); 361 EXPECT_FALSE(status); 391 addTestKeyToKeychain(); 362 392 363 393 WebCore::PublicKeyCredentialCreationOptions creationOptions; … … 476 506 auto& attestationCertificateData = x5c[0].getByteString(); 477 507 auto attestationCertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)[NSData dataWithBytes:attestationCertificateData.data() length:attestationCertificateData.size()])); 478 CFStringRef commonName = NULL;508 CFStringRef commonName = nullptr; 479 509 status = SecCertificateCopyCommonName(attestationCertificate.get(), &commonName); 480 510 auto retainCommonName = adoptCF(commonName); … … 484 514 auto& attestationIssuingCACertificateData = x5c[1].getByteString(); 485 515 auto attestationIssuingCACertificate = adoptCF(SecCertificateCreateWithData(NULL, (__bridge CFDataRef)[NSData dataWithBytes:attestationIssuingCACertificateData.data() length:attestationIssuingCACertificateData.size()])); 486 commonName = NULL;516 commonName = nullptr; 487 517 status = SecCertificateCopyCommonName(attestationIssuingCACertificate.get(), &commonName); 488 518 retainCommonName = adoptCF(commonName); … … 504 534 } 505 535 536 TEST(LocalAuthenticator, GetAssertionAllowCredentialsMismatch1) 537 { 538 // Transports mismatched 539 WebCore::PublicKeyCredentialDescriptor descriptor; 540 descriptor.type = WebCore::PublicKeyCredentialType::PublicKey; 541 descriptor.transports.append(WebCore::PublicKeyCredentialDescriptor::AuthenticatorTransport::Usb); 542 WebCore::PublicKeyCredentialRequestOptions requestOptions; 543 requestOptions.allowCredentials.append(WTFMove(descriptor)); 544 545 bool done = false; 546 std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>(); 547 auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) { 548 EXPECT_FALSE(true); 549 done = true; 550 }; 551 auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable { 552 EXPECT_EQ(WebCore::NotAllowedError, exception.code); 553 EXPECT_STREQ("No matched credentials are found in the platform attached authenticator.", exception.message.ascii().data()); 554 done = true; 555 }; 556 authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback)); 557 558 TestWebKitAPI::Util::run(&done); 559 } 560 561 TEST(LocalAuthenticator, GetAssertionAllowCredentialsMismatch2) 562 { 563 // No existing credential 564 WebCore::PublicKeyCredentialRequestOptions requestOptions; 565 requestOptions.rpId = testRpId; 566 567 bool done = false; 568 std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>(); 569 auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) { 570 EXPECT_FALSE(true); 571 done = true; 572 }; 573 auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable { 574 EXPECT_EQ(WebCore::NotAllowedError, exception.code); 575 EXPECT_STREQ("No matched credentials are found in the platform attached authenticator.", exception.message.ascii().data()); 576 done = true; 577 }; 578 authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback)); 579 580 TestWebKitAPI::Util::run(&done); 581 } 582 583 TEST(LocalAuthenticator, GetAssertionAllowCredentialsMismatch3) 584 { 585 // Credential ID mismatched 586 addTestKeyToKeychain(); 587 588 WebCore::PublicKeyCredentialDescriptor descriptor; 589 descriptor.type = WebCore::PublicKeyCredentialType::PublicKey; 590 WTF::base64Decode(testCredentialIdBase64, descriptor.idVector); 591 descriptor.idVector[19] = 0; // nuke the last byte. 592 WebCore::PublicKeyCredentialRequestOptions requestOptions; 593 requestOptions.rpId = testRpId; 594 requestOptions.allowCredentials.append(descriptor); 595 596 bool done = false; 597 std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>(); 598 auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) { 599 EXPECT_FALSE(true); 600 cleanUpKeychain(); 601 done = true; 602 }; 603 auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable { 604 EXPECT_EQ(WebCore::NotAllowedError, exception.code); 605 EXPECT_STREQ("No matched credentials are found in the platform attached authenticator.", exception.message.ascii().data()); 606 cleanUpKeychain(); 607 done = true; 608 }; 609 authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback)); 610 611 TestWebKitAPI::Util::run(&done); 612 } 613 614 TEST(LocalAuthenticator, GetAssertionBiometricsNotEnrolled) 615 { 616 LACantEvaluatePolicySwizzler swizzler; 617 618 addTestKeyToKeychain(); 619 620 WebCore::PublicKeyCredentialRequestOptions requestOptions; 621 requestOptions.rpId = testRpId; 622 623 bool done = false; 624 std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>(); 625 auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) { 626 EXPECT_FALSE(true); 627 cleanUpKeychain(); 628 done = true; 629 }; 630 auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable { 631 EXPECT_EQ(WebCore::NotAllowedError, exception.code); 632 EXPECT_STREQ("No avaliable authenticators.", exception.message.ascii().data()); 633 cleanUpKeychain(); 634 done = true; 635 }; 636 authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback)); 637 638 TestWebKitAPI::Util::run(&done); 639 } 640 641 TEST(LocalAuthenticator, GetAssertionBiometricsNotAuthenticated) 642 { 643 LACanEvaluatePolicySwizzler canEvaluatePolicySwizzler; 644 LAEvaluateAccessControlFailedSwizzler evaluateAccessControlFailedSwizzler; 645 646 addTestKeyToKeychain(); 647 648 WebCore::PublicKeyCredentialRequestOptions requestOptions; 649 requestOptions.rpId = testRpId; 650 651 bool done = false; 652 std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>(); 653 auto callback = [&done] (const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&, const Vector<uint8_t>&) { 654 EXPECT_FALSE(true); 655 cleanUpKeychain(); 656 done = true; 657 }; 658 auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable { 659 EXPECT_EQ(WebCore::NotAllowedError, exception.code); 660 EXPECT_STREQ("Couldn't get user consent.", exception.message.ascii().data()); 661 cleanUpKeychain(); 662 done = true; 663 }; 664 authenticator->getAssertion({ }, requestOptions, WTFMove(callback), WTFMove(exceptionCallback)); 665 666 TestWebKitAPI::Util::run(&done); 667 } 668 669 TEST(LocalAuthenticator, GetAssertionPassed) 670 { 671 LACanEvaluatePolicySwizzler canEvaluatePolicySwizzler; 672 LAEvaluateAccessControlPassedSwizzler evaluateAccessControlPassedSwizzler; 673 674 addTestKeyToKeychain(); 675 676 WebCore::PublicKeyCredentialRequestOptions requestOptions; 677 requestOptions.rpId = testRpId; 678 679 Vector<uint8_t> hash(32); 680 681 bool done = false; 682 std::unique_ptr<TestLocalAuthenticator> authenticator = std::make_unique<TestLocalAuthenticator>(); 683 auto callback = [&done, hash] (const Vector<uint8_t>& credentialId, const Vector<uint8_t>& authData, const Vector<uint8_t>& signature, const Vector<uint8_t>& userhandle) { 684 // Check Credential ID 685 EXPECT_TRUE(WTF::base64Encode(credentialId.data(), credentialId.size()) == testCredentialIdBase64); 686 687 // Check Authenticator Data. 688 size_t pos = 0; 689 uint8_t expectedRpIdHash[] = { 690 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, 0x8c, 0x68, 691 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, 0x60, 0x5b, 692 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, 0x32, 0xc7, 693 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, 0x97, 0x63 694 }; 695 EXPECT_FALSE(memcmp(authData.data() + pos, expectedRpIdHash, sizeof(expectedRpIdHash))); 696 pos += sizeof(expectedRpIdHash); 697 698 // FLAGS 699 EXPECT_EQ(5, authData[pos]); 700 pos++; 701 702 uint32_t counter = -1; 703 memcpy(&counter, authData.data() + pos, sizeof(uint32_t)); 704 EXPECT_EQ(0u, counter); 705 706 // Check signature 707 auto privateKey = getTestKey(); 708 Vector<uint8_t> dataToSign(authData); 709 dataToSign.appendVector(hash); 710 EXPECT_TRUE(SecKeyVerifySignature(SecKeyCopyPublicKey(privateKey.get()), kSecKeyAlgorithmECDSASignatureMessageX962SHA256, (__bridge CFDataRef)[NSData dataWithBytes:dataToSign.data() length:dataToSign.size()], (__bridge CFDataRef)[NSData dataWithBytes:signature.data() length:signature.size()], NULL)); 711 712 // Check User Handle 713 EXPECT_EQ(userhandle.size(), sizeof(testUserhandle)); 714 EXPECT_FALSE(memcmp(userhandle.data(), testUserhandle, sizeof(testUserhandle))); 715 716 cleanUpKeychain(); 717 done = true; 718 }; 719 auto exceptionCallback = [&done] (const WebCore::ExceptionData& exception) mutable { 720 EXPECT_FALSE(true); 721 cleanUpKeychain(); 722 done = true; 723 }; 724 authenticator->getAssertion(hash, requestOptions, WTFMove(callback), WTFMove(exceptionCallback)); 725 726 TestWebKitAPI::Util::run(&done); 727 } 728 506 729 } // namespace TestWebKitAPI 507 730
Note: See TracChangeset
for help on using the changeset viewer.