blob: f189f5967b440a537d2574d662cacbbf9bcbcff0 [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/make_credential_operation.h"
#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/utf_string_conversions.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/attestation_statement_formats.h"
#include "device/fido/attested_credential_data.h"
#include "device/fido/authenticator_data.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/mac/credential_metadata.h"
#include "device/fido/mac/keychain.h"
#include "device/fido/mac/util.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;
MakeCredentialOperation::MakeCredentialOperation(
CtapMakeCredentialRequest request,
std::string metadata_secret,
std::string keychain_access_group,
Callback callback)
: OperationBase<CtapMakeCredentialRequest,
AuthenticatorMakeCredentialResponse>(
std::move(request),
std::move(metadata_secret),
std::move(keychain_access_group),
std::move(callback)) {}
MakeCredentialOperation::~MakeCredentialOperation() = default;
const std::string& MakeCredentialOperation::RpId() const {
return request().rp.id;
}
void MakeCredentialOperation::Run() {
if (!Init()) {
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
// Verify pubKeyCredParams contains ES-256, which is the only algorithm we
// support.
auto is_es256 =
[](const PublicKeyCredentialParams::CredentialInfo& cred_info) {
return cred_info.algorithm ==
static_cast<int>(CoseAlgorithmIdentifier::kCoseEs256);
};
const auto& key_params =
request().public_key_credential_params.public_key_credential_params();
if (!std::any_of(key_params.begin(), key_params.end(), is_es256)) {
DVLOG(1) << "No supported algorithm found.";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrUnsupportedAlgorithm,
base::nullopt);
return;
}
// Display the macOS Touch ID prompt.
PromptTouchId(l10n_util::GetStringFUTF16(IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON,
base::UTF8ToUTF16(RpId())));
}
void MakeCredentialOperation::PromptTouchIdDone(bool success) {
if (!success) {
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt);
return;
}
// Evaluate that excludeList does not contain any credentials stored by this
// authenticator.
if (request().exclude_list) {
for (auto& credential : *request().exclude_list) {
ScopedCFTypeRef<CFMutableDictionaryRef> query = DefaultKeychainQuery();
CFDictionarySetValue(query, kSecAttrApplicationLabel,
[NSData dataWithBytes:credential.id().data()
length:credential.id().size()]);
OSStatus status = SecItemCopyMatching(query, nullptr);
if (status == errSecSuccess) {
// Excluded item found.
DVLOG(1) << "credential from excludeList found";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrCredentialExcluded,
base::nullopt);
return;
}
if (status != errSecItemNotFound) {
// Unexpected keychain error.
OSSTATUS_DLOG(ERROR, status)
<< "failed to check for excluded credential";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
}
}
// Delete the key pair for this RP + user handle if one already exists.
//
// Note that because the rk bit is not encoded here, a resident credential
// may overwrite a non-resident credential and vice versa.
base::Optional<std::string> encoded_rp_id_user_id =
EncodeRpIdAndUserId(metadata_secret(), RpId(), request().user.id);
if (!encoded_rp_id_user_id) {
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
{
ScopedCFTypeRef<CFMutableDictionaryRef> query = DefaultKeychainQuery();
CFDictionarySetValue(query, kSecAttrApplicationTag,
base::SysUTF8ToNSString(*encoded_rp_id_user_id));
OSStatus status = Keychain::GetInstance().ItemDelete(query);
if (status != errSecSuccess && status != errSecItemNotFound) {
OSSTATUS_DLOG(ERROR, status) << "SecItemDelete failed";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
}
// Generate the new key pair.
base::Optional<std::vector<uint8_t>> credential_id =
GenerateCredentialIdForRequest();
if (!credential_id) {
FIDO_LOG(ERROR) << "GenerateCredentialIdForRequest failed";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
ScopedCFTypeRef<CFMutableDictionaryRef> params(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
CFDictionarySetValue(params, kSecAttrKeyType,
kSecAttrKeyTypeECSECPrimeRandom);
CFDictionarySetValue(params, kSecAttrKeySizeInBits, @256);
CFDictionarySetValue(params, kSecAttrSynchronizable, @NO);
CFDictionarySetValue(params, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave);
ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params =
DefaultKeychainQuery();
CFDictionarySetValue(params, kSecPrivateKeyAttrs, private_key_params);
CFDictionarySetValue(private_key_params, kSecAttrIsPermanent, @YES);
CFDictionarySetValue(private_key_params, kSecAttrAccessControl,
access_control());
CFDictionarySetValue(private_key_params, kSecUseAuthenticationContext,
authentication_context());
CFDictionarySetValue(private_key_params, kSecAttrApplicationTag,
base::SysUTF8ToNSString(*encoded_rp_id_user_id));
CFDictionarySetValue(private_key_params, kSecAttrApplicationLabel,
[NSData dataWithBytes:credential_id->data()
length:credential_id->size()]);
ScopedCFTypeRef<CFErrorRef> cferr;
ScopedCFTypeRef<SecKeyRef> private_key(
Keychain::GetInstance().KeyCreateRandomKey(params,
cferr.InitializeInto()));
if (!private_key) {
FIDO_LOG(ERROR) << "SecKeyCreateRandomKey failed: " << cferr;
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
ScopedCFTypeRef<SecKeyRef> public_key(
Keychain::GetInstance().KeyCopyPublicKey(private_key));
if (!public_key) {
FIDO_LOG(ERROR) << "SecKeyCopyPublicKey failed";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
// Create attestation object. There is no separate attestation key pair, so
// we perform self-attestation.
base::Optional<AttestedCredentialData> attested_credential_data =
MakeAttestedCredentialData(*credential_id,
SecKeyRefToECPublicKey(public_key));
if (!attested_credential_data) {
FIDO_LOG(ERROR) << "MakeAttestedCredentialData failed";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
AuthenticatorData authenticator_data =
MakeAuthenticatorData(RpId(), std::move(*attested_credential_data));
base::Optional<std::vector<uint8_t>> signature = GenerateSignature(
authenticator_data, request().client_data_hash, private_key);
if (!signature) {
FIDO_LOG(ERROR) << "MakeSignature failed";
std::move(callback())
.Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
AuthenticatorMakeCredentialResponse response(
FidoTransportProtocol::kInternal,
AttestationObject(
std::move(authenticator_data),
std::make_unique<PackedAttestationStatement>(
CoseAlgorithmIdentifier::kCoseEs256, std::move(*signature),
/*x509_certificates=*/std::vector<std::vector<uint8_t>>())));
std::move(callback())
.Run(CtapDeviceResponseCode::kSuccess, std::move(response));
}
base::Optional<std::vector<uint8_t>>
MakeCredentialOperation::GenerateCredentialIdForRequest() const {
return SealCredentialId(metadata_secret(), RpId(),
CredentialMetadata::FromPublicKeyCredentialUserEntity(
request().user, request().resident_key_required));
}
} // namespace mac
} // namespace fido
} // namespace device