blob: 99fb012b428cd8cae187dbde5f63076c3613fcbb [file] [log] [blame]
// 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/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "device/fido/fido_constants.h"
#include "device/fido/mac/keychain.h"
#include "device/fido/mac/util.h"
#include "device/fido/public_key_credential_descriptor.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "device/fido/strings/grit/fido_strings.h"
#include "ui/base/l10n/l10n_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;
}
// Display the macOS Touch ID prompt.
PromptTouchId(l10n_util::GetStringFUTF16(IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON,
base::UTF8ToUTF16(RpId())));
}
void GetAssertionOperation::PromptTouchIdDone(bool success) {
if (!success) {
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt);
return;
}
// Collect the credential ids from allowList. If allowList is absent, we will
// pick the first available credential for the RP.
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;
if (!desc.transports().empty() &&
!base::ContainsKey(desc.transports(),
FidoTransportProtocol::kInternal))
continue;
allowed_credential_ids.insert(desc.id());
}
}
// Fetch credentials for RP from the request and current user profile.
base::Optional<Credential> credential = FindCredentialInKeychain(
keychain_access_group(), metadata_secret(), RpId(),
allowed_credential_ids, authentication_context());
if (!credential) {
// For now, don't show a Touch ID prompt if no credential exists.
// TODO(martinkr): Prompt for the fingerprint anyway, once dispatch to this
// authenticator is moved behind user interaction with the authenticator
// selection UI.
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt);
return;
}
// Decrypt the user entity from the credential ID.
base::Optional<CredentialMetadata::UserEntity> credential_user =
CredentialMetadata::UnsealCredentialId(metadata_secret(), RpId(),
credential->credential_id);
if (!credential_user) {
// The keychain query already filtered for the RP ID encoded under this
// operation's metadata secret, so the credential id really should have
// been decryptable.
DVLOG(1) << "UnsealCredentialId failed";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt);
return;
}
base::ScopedCFTypeRef<SecKeyRef> public_key(
Keychain::GetInstance().KeyCopyPublicKey(credential->private_key));
if (!public_key) {
DLOG(ERROR) << "failed to get public key for credential id "
<< base::HexEncode(credential->credential_id.data(),
credential->credential_id.size());
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
base::Optional<AuthenticatorData> authenticator_data = MakeAuthenticatorData(
RpId(), credential->credential_id, SecKeyRefToECPublicKey(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(),
credential->private_key);
if (!signature) {
DLOG(ERROR) << "GenerateSignature failed";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
auto response = AuthenticatorGetAssertionResponse(
std::move(*authenticator_data), std::move(*signature));
response.SetCredential(PublicKeyCredentialDescriptor(
CredentialType::kPublicKey, std::move(credential->credential_id)));
response.SetUserEntity(credential_user->ToPublicKeyCredentialUserEntity());
std::move(callback())
.Run(CtapDeviceResponseCode::kSuccess, std::move(response));
}
} // namespace mac
} // namespace fido
} // namespace device