| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "device/fido/mac/get_assertion_operation.h" |
| |
| #include <set> |
| #include <string> |
| |
| #import <Foundation/Foundation.h> |
| |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/mac_logging.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "device/fido/fido_constants.h" |
| #include "device/fido/mac/keychain.h" |
| #include "device/fido/mac/util.h" |
| |
| namespace device { |
| namespace fido { |
| namespace mac { |
| |
| using base::ScopedCFTypeRef; |
| |
| GetAssertionOperation::GetAssertionOperation(CtapGetAssertionRequest request, |
| std::string metadata_secret, |
| std::string keychain_access_group, |
| Callback callback) |
| : OperationBase<CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>( |
| std::move(request), |
| std::move(metadata_secret), |
| std::move(keychain_access_group), |
| std::move(callback)) {} |
| GetAssertionOperation::~GetAssertionOperation() = default; |
| |
| const std::string& GetAssertionOperation::RpId() const { |
| return request().rp_id(); |
| } |
| |
| void GetAssertionOperation::Run() { |
| if (!Init()) { |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); |
| return; |
| } |
| |
| // Prompt the user for consent. |
| // TODO(martinkr): Localize reason strings. |
| PromptTouchId("sign in to " + RpId()); |
| } |
| |
| void GetAssertionOperation::PromptTouchIdDone(bool success, NSError* err) { |
| if (!success) { |
| // err is autoreleased. |
| CHECK(err != nil); |
| DVLOG(1) << "Touch ID prompt failed: " << base::mac::NSToCFCast(err); |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt); |
| return; |
| } |
| |
| // Collect the credential ids from allowList. If allowList is absent, we will |
| // just pick the first available credential for the RP below. |
| std::set<std::vector<uint8_t>> allowed_credential_ids; |
| if (request().allow_list()) { |
| for (const PublicKeyCredentialDescriptor& desc : *request().allow_list()) { |
| if (desc.credential_type() != CredentialType::kPublicKey) { |
| continue; |
| } |
| allowed_credential_ids.insert(desc.id()); |
| } |
| } |
| |
| // Fetch credentials for RP from the request and current user profile. |
| ScopedCFTypeRef<CFArrayRef> keychain_items; |
| base::ScopedCFTypeRef<CFMutableDictionaryRef> query = DefaultKeychainQuery(); |
| CFDictionarySetValue(query, kSecUseAuthenticationContext, |
| authentication_context()); |
| CFDictionarySetValue(query, kSecReturnRef, @YES); |
| CFDictionarySetValue(query, kSecReturnAttributes, @YES); |
| CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll); |
| |
| OSStatus status = Keychain::GetInstance().ItemCopyMatching( |
| query, reinterpret_cast<CFTypeRef*>(keychain_items.InitializeInto())); |
| if (status == errSecItemNotFound) { |
| DVLOG(1) << "no credentials found for RP"; |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt); |
| return; |
| } |
| if (status != errSecSuccess) { |
| OSSTATUS_DLOG(ERROR, status) << "SecItemCopyMatching failed"; |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); |
| return; |
| } |
| SecKeyRef private_key = nil; // Owned by |keychain_items|. |
| std::vector<uint8_t> credential_id; |
| for (CFIndex i = 0; i < CFArrayGetCount(keychain_items); ++i) { |
| CFDictionaryRef attributes = base::mac::CFCast<CFDictionaryRef>( |
| CFArrayGetValueAtIndex(keychain_items, i)); |
| CFDataRef application_label = base::mac::GetValueFromDictionary<CFDataRef>( |
| attributes, kSecAttrApplicationLabel); |
| SecKeyRef key = |
| base::mac::GetValueFromDictionary<SecKeyRef>(attributes, kSecValueRef); |
| if (!application_label || !key) { |
| // Corrupted keychain? |
| DLOG(ERROR) << "could not find application label or key ref: " |
| << attributes; |
| continue; |
| } |
| std::vector<uint8_t> cid(CFDataGetBytePtr(application_label), |
| CFDataGetBytePtr(application_label) + |
| CFDataGetLength(application_label)); |
| if (allowed_credential_ids.empty() || |
| allowed_credential_ids.find(cid) != allowed_credential_ids.end()) { |
| private_key = key; |
| credential_id = std::move(cid); |
| break; |
| } |
| } |
| if (!private_key) { |
| DVLOG(1) << "no allowed credential found"; |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt); |
| return; |
| } |
| base::ScopedCFTypeRef<SecKeyRef> public_key( |
| Keychain::GetInstance().KeyCopyPublicKey(private_key)); |
| if (!public_key) { |
| DLOG(ERROR) << "failed to get public key for credential id " |
| << base::HexEncode(credential_id.data(), credential_id.size()); |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); |
| return; |
| } |
| |
| base::Optional<AuthenticatorData> authenticator_data = |
| MakeAuthenticatorData(RpId(), std::move(credential_id), public_key); |
| if (!authenticator_data) { |
| DLOG(ERROR) << "MakeAuthenticatorData failed"; |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); |
| return; |
| } |
| base::Optional<std::vector<uint8_t>> signature = GenerateSignature( |
| *authenticator_data, request().client_data_hash(), private_key); |
| if (!signature) { |
| DLOG(ERROR) << "GenerateSignature failed"; |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); |
| return; |
| } |
| std::move(callback()) |
| .Run(CtapDeviceResponseCode::kSuccess, |
| AuthenticatorGetAssertionResponse(std::move(*authenticator_data), |
| std::move(*signature))); |
| } |
| |
| } // namespace mac |
| } // namespace fido |
| } // namespace device |