Commit eaf85f18 authored by Martin Kreichgauer's avatar Martin Kreichgauer Committed by Commit Bot

//device/fido: add authenticator operations for Touch ID.

Bug: 678128
Change-Id: I77057cf8f630668d07fa93f9c4d09fefae187aa3
Reviewed-on: https://chromium-review.googlesource.com/1013054
Commit-Queue: Balazs Engedy <engedy@chromium.org>
Reviewed-by: default avatarNico Weber <thakis@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Reviewed-by: default avatarAdam Langley <agl@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555154}
parent 01b4a526
...@@ -56,8 +56,10 @@ typedef struct OpaqueSecTrustRef* SecACLRef; ...@@ -56,8 +56,10 @@ typedef struct OpaqueSecTrustRef* SecACLRef;
typedef struct OpaqueSecTrustedApplicationRef* SecTrustedApplicationRef; typedef struct OpaqueSecTrustedApplicationRef* SecTrustedApplicationRef;
#if defined(OS_IOS) #if defined(OS_IOS)
typedef struct CF_BRIDGED_TYPE(id) __SecKey* SecKeyRef;
typedef struct CF_BRIDGED_TYPE(id) __SecPolicy* SecPolicyRef; typedef struct CF_BRIDGED_TYPE(id) __SecPolicy* SecPolicyRef;
#else #else
typedef struct OpaqueSecKeyRef* SecKeyRef;
typedef struct OpaqueSecPolicyRef* SecPolicyRef; typedef struct OpaqueSecPolicyRef* SecPolicyRef;
#endif #endif
...@@ -147,6 +149,7 @@ TYPE_NAME_FOR_CF_TYPE_DECL(CGColor); ...@@ -147,6 +149,7 @@ TYPE_NAME_FOR_CF_TYPE_DECL(CGColor);
TYPE_NAME_FOR_CF_TYPE_DECL(CTFont); TYPE_NAME_FOR_CF_TYPE_DECL(CTFont);
TYPE_NAME_FOR_CF_TYPE_DECL(CTRun); TYPE_NAME_FOR_CF_TYPE_DECL(CTRun);
TYPE_NAME_FOR_CF_TYPE_DECL(SecKey);
TYPE_NAME_FOR_CF_TYPE_DECL(SecPolicy); TYPE_NAME_FOR_CF_TYPE_DECL(SecPolicy);
#undef TYPE_NAME_FOR_CF_TYPE_DECL #undef TYPE_NAME_FOR_CF_TYPE_DECL
...@@ -309,6 +312,7 @@ CF_CAST_DECL(CTFontDescriptor); ...@@ -309,6 +312,7 @@ CF_CAST_DECL(CTFontDescriptor);
CF_CAST_DECL(CTRun); CF_CAST_DECL(CTRun);
CF_CAST_DECL(SecACL); CF_CAST_DECL(SecACL);
CF_CAST_DECL(SecKey);
CF_CAST_DECL(SecPolicy); CF_CAST_DECL(SecPolicy);
CF_CAST_DECL(SecTrustedApplication); CF_CAST_DECL(SecTrustedApplication);
......
...@@ -21,13 +21,14 @@ ...@@ -21,13 +21,14 @@
#import <AppKit/AppKit.h> #import <AppKit/AppKit.h>
#endif #endif
#if !defined(OS_IOS)
extern "C" { extern "C" {
CFTypeID SecKeyGetTypeID();
#if !defined(OS_IOS)
CFTypeID SecACLGetTypeID(); CFTypeID SecACLGetTypeID();
CFTypeID SecTrustedApplicationGetTypeID(); CFTypeID SecTrustedApplicationGetTypeID();
Boolean _CFIsObjC(CFTypeID typeID, CFTypeRef obj); Boolean _CFIsObjC(CFTypeID typeID, CFTypeRef obj);
} // extern "C"
#endif #endif
} // extern "C"
namespace base { namespace base {
namespace mac { namespace mac {
...@@ -214,6 +215,7 @@ TYPE_NAME_FOR_CF_TYPE_DEFN(CTFont); ...@@ -214,6 +215,7 @@ TYPE_NAME_FOR_CF_TYPE_DEFN(CTFont);
TYPE_NAME_FOR_CF_TYPE_DEFN(CTRun); TYPE_NAME_FOR_CF_TYPE_DEFN(CTRun);
#if !defined(OS_IOS) #if !defined(OS_IOS)
TYPE_NAME_FOR_CF_TYPE_DEFN(SecKey);
TYPE_NAME_FOR_CF_TYPE_DEFN(SecPolicy); TYPE_NAME_FOR_CF_TYPE_DEFN(SecPolicy);
#endif #endif
...@@ -412,6 +414,7 @@ CFCastStrict<CTFontRef>(const CFTypeRef& cf_val) { ...@@ -412,6 +414,7 @@ CFCastStrict<CTFontRef>(const CFTypeRef& cf_val) {
#if !defined(OS_IOS) #if !defined(OS_IOS)
CF_CAST_DEFN(SecACL); CF_CAST_DEFN(SecACL);
CF_CAST_DEFN(SecKey);
CF_CAST_DEFN(SecPolicy); CF_CAST_DEFN(SecPolicy);
CF_CAST_DEFN(SecTrustedApplication); CF_CAST_DEFN(SecTrustedApplication);
#endif #endif
......
...@@ -76,6 +76,8 @@ test("device_unittests") { ...@@ -76,6 +76,8 @@ test("device_unittests") {
"fido/fido_request_handler_unittest.cc", "fido/fido_request_handler_unittest.cc",
"fido/get_assertion_handler_unittest.cc", "fido/get_assertion_handler_unittest.cc",
"fido/get_assertion_task_unittest.cc", "fido/get_assertion_task_unittest.cc",
"fido/mac/get_assertion_operation_unittest_mac.mm",
"fido/mac/make_credential_operation_unittest_mac.mm",
"fido/make_credential_handler_unittest.cc", "fido/make_credential_handler_unittest.cc",
"fido/make_credential_task_unittest.cc", "fido/make_credential_task_unittest.cc",
"fido/test_callback_receiver_unittest.cc", "fido/test_callback_receiver_unittest.cc",
......
...@@ -126,6 +126,8 @@ component("fido") { ...@@ -126,6 +126,8 @@ component("fido") {
"//services/device/public/mojom", "//services/device/public/mojom",
] ]
libs = [] # Extended for mac.
# HID is not supported on Android. # HID is not supported on Android.
if (!is_android) { if (!is_android) {
sources += [ sources += [
...@@ -140,6 +142,23 @@ component("fido") { ...@@ -140,6 +142,23 @@ component("fido") {
"//services/device/public/mojom", "//services/device/public/mojom",
] ]
} }
if (is_mac) {
sources += [
"mac/get_assertion_operation.h",
"mac/get_assertion_operation.mm",
"mac/make_credential_operation.h",
"mac/make_credential_operation.mm",
"mac/util.h",
"mac/util.mm",
]
libs += [
"Foundation.framework",
"LocalAuthentication.framework",
"Security.framework",
]
}
} }
source_set("mocks") { source_set("mocks") {
......
...@@ -48,7 +48,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestedCredentialData { ...@@ -48,7 +48,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestedCredentialData {
// * Credential Public Key. // * Credential Public Key.
std::vector<uint8_t> SerializeAsBytes() const; std::vector<uint8_t> SerializeAsBytes() const;
private:
static constexpr size_t kAaguidLength = 16; static constexpr size_t kAaguidLength = 16;
// Number of bytes used to represent length of credential ID. // Number of bytes used to represent length of credential ID.
static constexpr size_t kCredentialIdLengthLength = 2; static constexpr size_t kCredentialIdLengthLength = 2;
...@@ -59,6 +58,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestedCredentialData { ...@@ -59,6 +58,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestedCredentialData {
std::vector<uint8_t> credential_id, std::vector<uint8_t> credential_id,
std::unique_ptr<PublicKey> public_key); std::unique_ptr<PublicKey> public_key);
private:
// The 16-byte AAGUID of the authenticator. // The 16-byte AAGUID of the authenticator.
std::array<uint8_t, kAaguidLength> aaguid_; std::array<uint8_t, kAaguidLength> aaguid_;
......
...@@ -61,6 +61,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapMakeCredentialRequest { ...@@ -61,6 +61,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapMakeCredentialRequest {
return user_verification_required_; return user_verification_required_;
} }
bool resident_key_supported() const { return resident_key_supported_; } bool resident_key_supported() const { return resident_key_supported_; }
const base::Optional<std::vector<PublicKeyCredentialDescriptor>>&
exclude_list() const {
return exclude_list_;
}
private: private:
std::vector<uint8_t> client_data_hash_; std::vector<uint8_t> client_data_hash_;
......
...@@ -12,34 +12,39 @@ ...@@ -12,34 +12,39 @@
namespace device { namespace device {
namespace { namespace {
// The key is located after the first byte of the response // In a U2F registration response, the key is located after the first byte of
// (which is a reserved byte). It's in X9.62 format: // the response (which is a reserved byte). It's in X9.62 format:
// - a constant 0x04 prefix to indicate an uncompressed key // - a constant 0x04 prefix to indicate an uncompressed key
// - the 32-byte x coordinate // - the 32-byte x coordinate
// - the 32-byte y coordinate. // - the 32-byte y coordinate.
constexpr size_t kKeyCompressionTypeOffset = 1; constexpr size_t kReservedLength = 1;
constexpr size_t kUncompressedKey = 0x04; constexpr uint8_t kUncompressedKey = 0x04;
constexpr size_t kHeaderLength = 2; // Account for reserved byte and prefix. constexpr size_t kFieldElementLength = 32;
constexpr size_t kKeyLength = 32;
} // namespace } // namespace
// static // static
std::unique_ptr<ECPublicKey> ECPublicKey::ExtractFromU2fRegistrationResponse( std::unique_ptr<ECPublicKey> ECPublicKey::ExtractFromU2fRegistrationResponse(
std::string algorithm, std::string algorithm,
base::span<const uint8_t> u2f_data) { base::span<const uint8_t> u2f_data) {
if (u2f_data.size() < kHeaderLength || return ParseX962Uncompressed(
u2f_data[kKeyCompressionTypeOffset] != kUncompressedKey) std::move(algorithm),
return nullptr; fido_parsing_utils::ExtractSuffixSpan(u2f_data, kReservedLength));
}
std::vector<uint8_t> x = // static
fido_parsing_utils::Extract(u2f_data, kHeaderLength, kKeyLength); std::unique_ptr<ECPublicKey> ECPublicKey::ParseX962Uncompressed(
std::string algorithm,
base::span<const uint8_t> input) {
if (input.empty() || input[0] != kUncompressedKey)
return nullptr;
const std::vector<uint8_t> x =
fido_parsing_utils::Extract(input, 1, kFieldElementLength);
if (x.empty()) if (x.empty())
return nullptr; return nullptr;
std::vector<uint8_t> y = fido_parsing_utils::Extract( const std::vector<uint8_t> y = fido_parsing_utils::Extract(
u2f_data, kHeaderLength + kKeyLength, kKeyLength); input, 1 + kFieldElementLength, kFieldElementLength);
if (y.empty()) if (y.empty())
return nullptr; return nullptr;
...@@ -53,8 +58,8 @@ ECPublicKey::ECPublicKey(std::string algorithm, ...@@ -53,8 +58,8 @@ ECPublicKey::ECPublicKey(std::string algorithm,
: PublicKey(std::move(algorithm)), : PublicKey(std::move(algorithm)),
x_coordinate_(std::move(x)), x_coordinate_(std::move(x)),
y_coordinate_(std::move(y)) { y_coordinate_(std::move(y)) {
DCHECK_EQ(x_coordinate_.size(), kKeyLength); DCHECK_EQ(x_coordinate_.size(), kFieldElementLength);
DCHECK_EQ(y_coordinate_.size(), kKeyLength); DCHECK_EQ(y_coordinate_.size(), kFieldElementLength);
} }
ECPublicKey::~ECPublicKey() = default; ECPublicKey::~ECPublicKey() = default;
......
...@@ -26,6 +26,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) ECPublicKey : public PublicKey { ...@@ -26,6 +26,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) ECPublicKey : public PublicKey {
std::string algorithm, std::string algorithm,
base::span<const uint8_t> u2f_data); base::span<const uint8_t> u2f_data);
// Parse a public key encoded in ANSI X9.62 uncompressed format.
static std::unique_ptr<ECPublicKey> ParseX962Uncompressed(
std::string algorithm,
base::span<const uint8_t> input);
ECPublicKey(std::string algorithm, ECPublicKey(std::string algorithm,
std::vector<uint8_t> x, std::vector<uint8_t> x,
std::vector<uint8_t> y); std::vector<uint8_t> y);
......
// 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.
#ifndef DEVICE_FIDO_MAC_GET_ASSERTION_OPERATION_H_
#define DEVICE_FIDO_MAC_GET_ASSERTION_OPERATION_H_
#import <LocalAuthentication/LocalAuthentication.h>
#import <Security/Security.h>
#include "base/callback.h"
#include "base/component_export.h"
#include "base/mac/availability.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/fido_constants.h"
namespace device {
namespace fido {
namespace mac {
// GetAssertionOperation implements the authenticatorGetAssertion operation. The
// operation can be invoked via its |Run| method, which must only be called
// once.
//
// It prompts the user for consent via Touch ID, then looks up a key pair
// matching the request in the keychain and generates an assertion.
//
// For documentation on the keychain item metadata, see
// |MakeCredentialOperation|.
class API_AVAILABLE(macosx(10.12.2))
COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionOperation {
public:
using Callback = base::OnceCallback<void(
CtapDeviceResponseCode,
base::Optional<AuthenticatorGetAssertionResponse>)>;
GetAssertionOperation(CtapGetAssertionRequest request,
std::string profile_id,
std::string keychain_access_group,
Callback callback);
GetAssertionOperation(GetAssertionOperation&&);
GetAssertionOperation& operator=(GetAssertionOperation&&);
~GetAssertionOperation();
void Run();
private:
// Callback for `LAContext evaluateAccessControl`. Any NSError that gets
// passed is autoreleased.
void PromptTouchIdDone(bool success, NSError* err);
// profile_id_ identifies the user profile from which the request originates.
// TODO(martinkr): Figure out how to generate and store this (PrefService?).
std::string profile_id_;
std::string keychain_access_group_;
CtapGetAssertionRequest request_;
Callback callback_;
base::scoped_nsobject<LAContext> context_;
base::ScopedCFTypeRef<SecAccessControlRef> access_;
};
} // namespace mac
} // namespace fido
} // namespace device
#endif // DEVICE_FIDO_MAC_GET_ASSERTION_OPERATION_H_
// 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/bind.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 "base/strings/sys_string_conversions.h"
#include "device/fido/ec_public_key.h"
#include "device/fido/fido_attestation_statement.h"
#include "device/fido/fido_constants.h"
#include "device/fido/mac/util.h"
namespace device {
namespace fido {
namespace mac {
using base::ScopedCFTypeRef;
GetAssertionOperation::GetAssertionOperation(
CtapGetAssertionRequest request,
std::string profile_id,
std::string keychain_access_group,
GetAssertionOperation::Callback callback)
: profile_id_(std::move(profile_id)),
keychain_access_group_(std::move(keychain_access_group)),
request_(std::move(request)),
callback_(std::move(callback)),
context_([[LAContext alloc] init]),
access_(nil) {}
GetAssertionOperation::GetAssertionOperation(GetAssertionOperation&&) = default;
GetAssertionOperation& GetAssertionOperation::operator=(
GetAssertionOperation&&) = default;
GetAssertionOperation::~GetAssertionOperation() = default;
void GetAssertionOperation::Run() {
// Prompt the user for consent. The SecAccessControlRef used for the Touch ID
// prompt is stashed away in |access_| so it can be reused for signing the
// attestation object later, without triggering a second Touch ID prompt.
//
// N.B. that the CTAP spec explicitly says consent has to be acquired *first*
// before trying to match credentials, while webauthn does it later.
ScopedCFTypeRef<CFErrorRef> cferr;
access_.reset(SecAccessControlCreateWithFlags(
kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecAccessControlPrivateKeyUsage | kSecAccessControlTouchIDAny,
cferr.InitializeInto()));
if (!access_) {
DLOG(ERROR) << "SecAccessControlCreateWithFlags failed: " << cferr;
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
base::nullopt);
return;
}
// TODO(martinkr): Localize reason strings.
std::string reason = "sign in to " + request_.rp_id();
// TODO(martinkr): The callback block implicitly captures |this|, which is a
// use-after-destroy issue. The TouchIdContext class extracted in a follow-up
// CL holds a WeakPtr instead.
[context_ evaluateAccessControl:access_
operation:LAAccessControlOperationUseKeySign
localizedReason:base::SysUTF8ToNSString(reason)
reply:^(BOOL success, NSError* error) {
PromptTouchIdDone(success, error);
}];
}
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() != "public-key") {
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(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
CFDictionarySetValue(query, kSecClass, kSecClassKey);
CFDictionarySetValue(query, kSecAttrKeyClass, kSecAttrKeyClassPrivate);
CFDictionarySetValue(query, kSecAttrLabel,
base::SysUTF8ToNSString(request_.rp_id()));
CFDictionarySetValue(query, kSecAttrApplicationTag,
base::SysUTF8ToNSString(profile_id_));
CFDictionarySetValue(query, kSecUseAuthenticationContext, context_);
CFDictionarySetValue(query, kSecReturnRef, @YES);
CFDictionarySetValue(query, kSecReturnAttributes, @YES);
CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
OSStatus status = SecItemCopyMatching(
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(SecKeyCopyPublicKey(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(
request_.client_data_hash(), 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
// 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 <Foundation/Foundation.h>
#include <Security/Security.h>
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_task_environment.h"
#include "device/fido/mac/make_credential_operation.h"
#include "device/fido/test_callback_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace fido {
namespace mac {
namespace {
using test::TestCallbackReceiver;
const std::vector<uint8_t> kClientDataHash = {1, 2, 3, 4, 5};
const std::string kRpId = "rp.example.com";
const std::vector<uint8_t> kUserId = {10, 11, 12, 13, 14, 15};
const char kKeychainAccessGroup[] =
"EQHXZ8M8AV.com.google.chrome.webauthn.test";
CtapGetAssertionRequest MakeTestRequest() {
return CtapGetAssertionRequest(kRpId, kClientDataHash);
}
bool MakeCredential() API_AVAILABLE(macos(10.12.2)) {
TestCallbackReceiver<CtapDeviceResponseCode,
base::Optional<AuthenticatorMakeCredentialResponse>>
callback_receiver;
auto request = CtapMakeCredentialRequest(
kClientDataHash, PublicKeyCredentialRpEntity(kRpId),
PublicKeyCredentialUserEntity(kUserId),
PublicKeyCredentialParams(
{{PublicKeyCredentialParams::
CredentialInfo() /* defaults to ES-256 */}}));
MakeCredentialOperation op(request, "test-profile", kKeychainAccessGroup,
callback_receiver.callback());
op.Run();
callback_receiver.WaitForCallback();
auto result = callback_receiver.TakeResult();
CtapDeviceResponseCode error = std::get<0>(result);
auto opt_response = std::move(std::get<1>(result));
return error == CtapDeviceResponseCode::kSuccess && opt_response;
}
// For demo purposes only. This test does a Touch ID user prompt. It will fail
// on incompatible hardware and crash if not code signed or lacking the
// keychain-access-group entitlement.
TEST(GetAssertionOperationTest, DISABLED_TestRun)
API_AVAILABLE(macos(10.12.2)) {
base::test::ScopedTaskEnvironment scoped_task_environment;
ASSERT_TRUE(MakeCredential());
TestCallbackReceiver<CtapDeviceResponseCode,
base::Optional<AuthenticatorGetAssertionResponse>>
callback_receiver;
auto request = MakeTestRequest();
GetAssertionOperation op(request, "test-profile", kKeychainAccessGroup,
callback_receiver.callback());
op.Run();
callback_receiver.WaitForCallback();
auto result = callback_receiver.TakeResult();
CtapDeviceResponseCode error = std::get<0>(result);
EXPECT_EQ(CtapDeviceResponseCode::kSuccess, error);
auto opt_response = std::move(std::get<1>(result));
ASSERT_TRUE(opt_response);
};
}
} // namespace mac
} // namespace fido
} // namespace device
// 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.
#ifndef DEVICE_FIDO_MAC_MAKE_CREDENTIAL_OPERATION_H_
#define DEVICE_FIDO_MAC_MAKE_CREDENTIAL_OPERATION_H_
#import <LocalAuthentication/LocalAuthentication.h>
#import <Security/Security.h>
#include "base/callback.h"
#include "base/component_export.h"
#include "base/mac/availability.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "device/fido/authenticator_make_credential_response.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_constants.h"
namespace device {
namespace fido {
namespace mac {
// MakeCredentialOperation implements the authenticatorMakeCredential operation.
// The operation can be invoked via its |Run| method, which must only be called
// once.
//
// It prompts the user for consent via Touch ID and then generates a key pair
// in the secure enclave. A reference to the private key is stored as a
// keychain item in the macOS keychain for later lookup. The actual private key
// cannot be extracted from the secure enclave. Each keychain item stores the
// following metadata:
//
// - The item's application label (kSecAttrApplicationLabel), which must be
// unique, contains the credential identifier, which is computed as the CBOR
// encoding of (rp_id, user_id).
//
// - The application tag (kSecAttrApplicationTag) holds an identifier for the
// associated Chrome user profile, in order to separate credentials from
// different profiles.
//
// - The label (kSecAttrLabel) stores the RP ID, to allow iteration over all
// keys by a given RP.
//
// Keychain items are stored with the access group (kSecAttrAccessGroup) set
// to a value that identifies them as Chrome WebAuthn credentials
// (keychain_access_group_), so that they are logically
// separate from any other data that Chrome may store in the keychain in
// the future.
class API_AVAILABLE(macosx(10.12.2))
COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialOperation {
public:
using Callback = base::OnceCallback<void(
CtapDeviceResponseCode,
base::Optional<AuthenticatorMakeCredentialResponse>)>;
MakeCredentialOperation(CtapMakeCredentialRequest request,
std::string profile_id,
std::string keychain_access_group,
Callback callback);
MakeCredentialOperation(MakeCredentialOperation&&);
MakeCredentialOperation& operator=(MakeCredentialOperation&&);
~MakeCredentialOperation();
void Run();
private:
// Callback for `LAContext evaluateAccessControl`. Any NSError that gets
// passed is autoreleased.
void PromptTouchIdDone(bool success, NSError* err);
// An identifier for the user profile from which the request originates.
// TODO(martinkr): Figure out how to generate and store this (PrefService?).
std::string profile_id_;
// The value for kSecAttrAccessGroup, which acts as a logical grouping of all
// WebAuthn credentials. May be different in tests.
std::string keychain_access_group_;
CtapMakeCredentialRequest request_;
Callback callback_;
base::scoped_nsobject<LAContext> context_;
base::ScopedCFTypeRef<SecAccessControlRef> access_;
};
} // namespace mac
} // namespace fido
} // namespace device
#endif // DEVICE_FIDO_MAC_MAKE_CREDENTIAL_OPERATION_H_
This diff is collapsed.
// 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 <Foundation/Foundation.h>
#include <Security/Security.h>
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_task_environment.h"
#include "device/fido/test_callback_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace fido {
namespace mac {
namespace {
using test::TestCallbackReceiver;
const std::vector<uint8_t> kClientDataHash = {1, 2, 3, 4, 5};
const std::string kRpId = "rp.example.com";
const std::vector<uint8_t> kUserId = {10, 11, 12, 13, 14, 15};
const char kKeychainAccessGroup[] =
"EQHXZ8M8AV.com.google.chrome.webauthn.test";
CtapMakeCredentialRequest MakeTestRequest() {
return CtapMakeCredentialRequest(
kClientDataHash, PublicKeyCredentialRpEntity(kRpId),
PublicKeyCredentialUserEntity(kUserId),
PublicKeyCredentialParams(
{{PublicKeyCredentialParams::
CredentialInfo() /* defaults to ES-256 */}}));
}
// For demo purposes only. This test does a Touch ID user prompt. It will fail
// on incompatible hardware and crash if not code signed or lacking the
// keychain-access-group entitlement.
TEST(MakeCredentialOperationTest, DISABLED_TestRun)
API_AVAILABLE(macosx(10.12.2)) {
base::test::ScopedTaskEnvironment scoped_task_environment;
TestCallbackReceiver<CtapDeviceResponseCode,
base::Optional<AuthenticatorMakeCredentialResponse>>
callback_receiver;
auto request = MakeTestRequest();
MakeCredentialOperation op(request, "test-profile", kKeychainAccessGroup,
callback_receiver.callback());
op.Run();
callback_receiver.WaitForCallback();
auto result = callback_receiver.TakeResult();
CtapDeviceResponseCode error = std::get<0>(result);
EXPECT_EQ(CtapDeviceResponseCode::kSuccess, error);
auto opt_response = std::move(std::get<1>(result));
ASSERT_TRUE(opt_response);
};
} // namespace
} // namespace mac
} // namespace fido
} // namespace device
// 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.
#ifndef DEVICE_FIDO_MAC_UTIL_H_
#define DEVICE_FIDO_MAC_UTIL_H_
#include <string>
#include <vector>
#import <Security/Security.h>
#include "base/callback.h"
#include "base/mac/availability.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsobject.h"
namespace device {
namespace fido {
namespace mac {
// KeychainItemIdentifier returns the unique identifier for a key pair, derived
// from an RP ID and user handle. It is stored in the keychain items
// kSecAttrApplicationLabel attribute and can be used for lookup.
std::vector<uint8_t> KeychainItemIdentifier(std::string rp_id,
std::vector<uint8_t> user_id);
// MakeAuthenticatorData returns an AuthenticatorData instance for the Touch ID
// authenticator with the given client data, credential id and public key. It
// returns |base::nullopt| on failure.
base::Optional<AuthenticatorData> MakeAuthenticatorData(
std::vector<uint8_t> client_data_hash,
std::vector<uint8_t> credential_id,
SecKeyRef public_key) API_AVAILABLE(macosx(10.12.2));
// GenerateSignature signs the concatenation of the serialization of the given
// authenticator data and the given client data hash, as required for
// (self-)attestation and assertion. Returns |base::nullopt| if the operation
// fails.
base::Optional<std::vector<uint8_t>> GenerateSignature(
const AuthenticatorData& authenticator_data,
const std::vector<uint8_t>& client_data_hash,
SecKeyRef private_key) API_AVAILABLE(macosx(10.12.2));
} // namespace mac
} // namespace fido
} // namespace device
#endif // DEVICE_FIDO_MAC_UTIL_H_
// 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 <array>
#include <set>
#include <string>
#import <Foundation/Foundation.h>
#include "base/bind.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsobject.h"
#include "base/strings/string_number_conversions.h"
#include "components/cbor/cbor_writer.h"
#include "device/fido/ec_public_key.h"
#include "device/fido/fido_attestation_statement.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
namespace device {
namespace fido {
namespace mac {
using base::ScopedCFTypeRef;
using base::scoped_nsobject;
using cbor::CBORWriter;
using cbor::CBORValue;
// The authenticator AAGUID value.
constexpr std::array<uint8_t, 16> kAaguid = {0xad, 0xce, 0x00, 0x02, 0x35, 0xbc,
0xc6, 0x0a, 0x64, 0x8b, 0x0b, 0x25,
0xf1, 0xf0, 0x55, 0x03};
namespace {
// MakeECPublicKey converts a SecKeyRef for a public key into an equivalent
// |ECPublicKey| instance. It returns |nullptr| if the key cannot be converted.
std::unique_ptr<ECPublicKey> MakeECPublicKey(SecKeyRef public_key_ref)
API_AVAILABLE(macosx(10.12.2)) {
CHECK(public_key_ref);
ScopedCFTypeRef<CFErrorRef> err;
ScopedCFTypeRef<CFDataRef> data_ref(
SecKeyCopyExternalRepresentation(public_key_ref, err.InitializeInto()));
if (!data_ref) {
LOG(ERROR) << "SecCopyExternalRepresentation failed: " << err;
return nullptr;
}
base::span<const uint8_t> key_data =
base::make_span(CFDataGetBytePtr(data_ref), CFDataGetLength(data_ref));
auto key =
ECPublicKey::ParseX962Uncompressed(fido_parsing_utils::kEs256, key_data);
if (!key) {
LOG(ERROR) << "Unexpected public key format: "
<< base::HexEncode(key_data.data(), key_data.size());
return nullptr;
}
return key;
}
} // namespace
// KeychainItemIdentifier returns the unique identifier for a given RP ID
// and user handle. It is stored in the keychain items Application Label and
// used for later lookup.
std::vector<uint8_t> KeychainItemIdentifier(std::string rp_id,
std::vector<uint8_t> user_id) {
std::vector<CBORValue> array;
array.emplace_back(CBORValue(rp_id));
array.emplace_back(CBORValue(user_id));
auto value = CBORWriter::Write(CBORValue(std::move(array)));
CHECK(value);
return *value;
}
base::Optional<AuthenticatorData> MakeAuthenticatorData(
std::vector<uint8_t> client_data_hash,
std::vector<uint8_t> credential_id,
SecKeyRef public_key) API_AVAILABLE(macosx(10.12.2)) {
if (credential_id.size() > 255) {
LOG(ERROR) << "credential id too long: "
<< base::HexEncode(credential_id.data(), credential_id.size());
return base::nullopt;
}
std::array<uint8_t, 2> encoded_credential_id_length = {
0, static_cast<uint8_t>(credential_id.size())};
constexpr uint8_t flags =
static_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserVerification) |
static_cast<uint8_t>(AuthenticatorData::Flag::kAttestation);
std::vector<uint8_t> counter = {0, 0, 0, 0}; // implement
auto ec_public_key = MakeECPublicKey(public_key);
if (!ec_public_key) {
LOG(ERROR) << "MakeECPublicKey failed";
return base::nullopt;
}
return AuthenticatorData(
std::move(client_data_hash), flags, counter,
AttestedCredentialData(kAaguid, encoded_credential_id_length,
std::move(credential_id),
std::move(ec_public_key)));
}
base::Optional<std::vector<uint8_t>> GenerateSignature(
const AuthenticatorData& authenticator_data,
const std::vector<uint8_t>& client_data_hash,
SecKeyRef private_key) API_AVAILABLE(macosx(10.12.2)) {
const std::vector<uint8_t> serialized_authenticator_data =
authenticator_data.SerializeToByteArray();
size_t capacity =
serialized_authenticator_data.size() + client_data_hash.size();
ScopedCFTypeRef<CFMutableDataRef> sig_input(
CFDataCreateMutable(kCFAllocatorDefault, capacity));
CFDataAppendBytes(sig_input, serialized_authenticator_data.data(),
serialized_authenticator_data.size());
CFDataAppendBytes(sig_input, client_data_hash.data(),
client_data_hash.size());
ScopedCFTypeRef<CFErrorRef> err;
ScopedCFTypeRef<CFDataRef> sig_data(SecKeyCreateSignature(
private_key, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, sig_input,
err.InitializeInto()));
if (!sig_data) {
LOG(ERROR) << "SecKeyCreateSignature failed: " << err;
return base::nullopt;
}
return std::vector<uint8_t>(
CFDataGetBytePtr(sig_data),
CFDataGetBytePtr(sig_data) + CFDataGetLength(sig_data));
}
} // namespace mac
} // namespace fido
} // namespace device
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment