Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 1 | // Copyright 2024 The Chromium Authors |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 5 | #include "crypto/user_verifying_key.h" |
| 6 | |
| 7 | #import <LocalAuthentication/LocalAuthentication.h> |
| 8 | |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 9 | #include <atomic> |
| 10 | #include <functional> |
| 11 | #include <memory> |
| 12 | #include <utility> |
| 13 | |
| 14 | #include "base/functional/bind.h" |
| 15 | #include "base/functional/callback_helpers.h" |
| 16 | #include "base/location.h" |
| 17 | #include "base/logging.h" |
| 18 | #include "base/memory/scoped_refptr.h" |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 19 | #include "base/task/bind_post_task.h" |
| 20 | #include "base/task/single_thread_task_runner.h" |
| 21 | #include "base/task/thread_pool.h" |
| 22 | #include "base/threading/scoped_thread_priority.h" |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 23 | #include "base/types/expected.h" |
Elly Fong-Jones | 38c29b6 | 2025-07-23 22:54:53 | [diff] [blame] | 24 | #include "crypto/apple/keychain_v2.h" |
Nina Satragno | dae4492 | 2024-03-22 21:20:07 | [diff] [blame] | 25 | #include "crypto/scoped_lacontext.h" |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 26 | #include "crypto/unexportable_key.h" |
Nina Satragno | dae4492 | 2024-03-22 21:20:07 | [diff] [blame] | 27 | #include "crypto/unexportable_key_mac.h" |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 28 | |
| 29 | namespace crypto { |
| 30 | |
| 31 | namespace { |
| 32 | |
| 33 | // Refcounted wrapper for UnexportableSigningKey. |
| 34 | class RefCountedUnexportableSigningKey |
| 35 | : public base::RefCountedThreadSafe<RefCountedUnexportableSigningKey> { |
| 36 | public: |
| 37 | explicit RefCountedUnexportableSigningKey( |
| 38 | std::unique_ptr<UnexportableSigningKey> key) |
| 39 | : key_(std::move(key)) {} |
| 40 | |
| 41 | UnexportableSigningKey* key() { return key_.get(); } |
| 42 | |
| 43 | private: |
| 44 | friend class base::RefCountedThreadSafe<RefCountedUnexportableSigningKey>; |
| 45 | ~RefCountedUnexportableSigningKey() = default; |
| 46 | |
| 47 | std::unique_ptr<UnexportableSigningKey> key_; |
| 48 | }; |
| 49 | |
| 50 | // Wraps signing |data| with |key|. |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 51 | base::expected<std::vector<uint8_t>, UserVerifyingKeySigningError> DoSign( |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 52 | std::vector<uint8_t> data, |
| 53 | scoped_refptr<RefCountedUnexportableSigningKey> key) { |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 54 | auto opt_signature = key->key()->SignSlowly(data); |
| 55 | if (!opt_signature.has_value()) { |
| 56 | return base::unexpected(UserVerifyingKeySigningError::kUnknownError); |
| 57 | } |
| 58 | return base::ok(*opt_signature); |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 59 | } |
| 60 | |
| 61 | std::string ToString(const std::vector<uint8_t>& vec) { |
| 62 | return std::string(vec.begin(), vec.end()); |
| 63 | } |
| 64 | |
| 65 | // User verifying key implementation that delegates the heavy lifting to |
| 66 | // UnexportableKeyMac. |
| 67 | class UserVerifyingSigningKeyMac : public UserVerifyingSigningKey { |
| 68 | public: |
| 69 | explicit UserVerifyingSigningKeyMac( |
| 70 | std::unique_ptr<UnexportableSigningKey> key) |
| 71 | : key_name_(ToString(key->GetWrappedKey())), |
| 72 | key_(base::MakeRefCounted<RefCountedUnexportableSigningKey>( |
| 73 | std::move(key))) {} |
| 74 | ~UserVerifyingSigningKeyMac() override = default; |
| 75 | |
| 76 | void Sign(base::span<const uint8_t> data, |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 77 | UserVerifyingKeySignatureCallback callback) override { |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 78 | // Signing will result in the system TouchID prompt being shown if the |
| 79 | // caller does not pass an authenticated LAContext, so we run the signing |
| 80 | // code in a separate thread. |
| 81 | scoped_refptr<base::SequencedTaskRunner> worker_task_runner = |
| 82 | base::ThreadPool::CreateSequencedTaskRunner( |
| 83 | {base::MayBlock(), base::TaskPriority::USER_BLOCKING}); |
| 84 | |
| 85 | // Copy |data| to avoid needing to guarantee its backing storage to outlive |
| 86 | // the thread. |
| 87 | std::vector<uint8_t> data_copy(data.begin(), data.end()); |
| 88 | |
| 89 | worker_task_runner->PostTaskAndReplyWithResult( |
| 90 | FROM_HERE, base::BindOnce(&DoSign, std::move(data_copy), key_), |
| 91 | std::move(callback)); |
| 92 | } |
| 93 | |
| 94 | std::vector<uint8_t> GetPublicKey() const override { |
| 95 | return key_->key()->GetSubjectPublicKeyInfo(); |
| 96 | } |
| 97 | |
| 98 | const UserVerifyingKeyLabel& GetKeyLabel() const override { |
| 99 | return key_name_; |
| 100 | } |
| 101 | |
Adam Langley | 884e6c4 | 2024-08-02 19:44:23 | [diff] [blame] | 102 | bool IsHardwareBacked() const override { return true; } |
| 103 | |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 104 | private: |
| 105 | // The key's wrapped key as a binary string. |
| 106 | const std::string key_name_; |
| 107 | const scoped_refptr<RefCountedUnexportableSigningKey> key_; |
| 108 | }; |
| 109 | |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 110 | base::expected<std::unique_ptr<UserVerifyingSigningKey>, |
| 111 | UserVerifyingKeyCreationError> |
| 112 | DoGenerateKey(base::span<const SignatureVerifier::SignatureAlgorithm> |
| 113 | acceptable_algorithms, |
| 114 | UnexportableKeyProvider::Config config, |
| 115 | LAContext* lacontext) { |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 116 | std::unique_ptr<UnexportableKeyProviderMac> key_provider = |
| 117 | GetUnexportableKeyProviderMac(std::move(config)); |
| 118 | if (!key_provider) { |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 119 | return base::unexpected(UserVerifyingKeyCreationError::kPlatformApiError); |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 120 | } |
| 121 | |
| 122 | std::unique_ptr<UnexportableSigningKey> key = |
| 123 | key_provider->GenerateSigningKeySlowly(acceptable_algorithms, lacontext); |
| 124 | if (!key) { |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 125 | return base::unexpected(UserVerifyingKeyCreationError::kPlatformApiError); |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 126 | } |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 127 | return base::ok(std::make_unique<UserVerifyingSigningKeyMac>(std::move(key))); |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 128 | } |
| 129 | |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 130 | base::expected<std::unique_ptr<UserVerifyingSigningKey>, |
| 131 | UserVerifyingKeyCreationError> |
| 132 | DoGetKey(std::vector<uint8_t> wrapped_key, |
| 133 | UnexportableKeyProvider::Config config, |
| 134 | LAContext* lacontext) { |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 135 | std::unique_ptr<UnexportableKeyProviderMac> key_provider = |
| 136 | GetUnexportableKeyProviderMac(std::move(config)); |
| 137 | if (!key_provider) { |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 138 | return base::unexpected(UserVerifyingKeyCreationError::kPlatformApiError); |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 139 | } |
| 140 | std::unique_ptr<UnexportableSigningKey> key = |
| 141 | key_provider->FromWrappedSigningKeySlowly(wrapped_key, lacontext); |
| 142 | if (!key) { |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 143 | return base::unexpected(UserVerifyingKeyCreationError::kPlatformApiError); |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 144 | } |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 145 | return base::ok(std::make_unique<UserVerifyingSigningKeyMac>(std::move(key))); |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 146 | } |
| 147 | |
| 148 | bool DoDeleteKey(std::vector<uint8_t> wrapped_key, |
| 149 | UnexportableKeyProvider::Config config) { |
| 150 | std::unique_ptr<UnexportableKeyProvider> key_provider = |
| 151 | GetUnexportableKeyProvider(std::move(config)); |
| 152 | if (!key_provider) { |
| 153 | return false; |
| 154 | } |
Ken Buchanan | e9f04b3 | 2024-04-26 19:50:06 | [diff] [blame] | 155 | return key_provider->DeleteSigningKeySlowly(wrapped_key); |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 156 | } |
| 157 | |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 158 | class UserVerifyingKeyProviderMac : public UserVerifyingKeyProvider { |
| 159 | public: |
| 160 | explicit UserVerifyingKeyProviderMac(UserVerifyingKeyProvider::Config config) |
Nina Satragno | dae4492 | 2024-03-22 21:20:07 | [diff] [blame] | 161 | : lacontext_(config.lacontext ? config.lacontext->release() : nil), |
| 162 | config_(std::move(config)) {} |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 163 | ~UserVerifyingKeyProviderMac() override = default; |
| 164 | |
| 165 | void GenerateUserVerifyingSigningKey( |
| 166 | base::span<const SignatureVerifier::SignatureAlgorithm> |
| 167 | acceptable_algorithms, |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 168 | UserVerifyingKeyCreationCallback callback) override { |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 169 | // Creating a key may result in disk access, so do it in a separate thread |
| 170 | // to avoid blocking the UI. |
| 171 | scoped_refptr<base::SequencedTaskRunner> worker_task_runner = |
| 172 | base::ThreadPool::CreateSequencedTaskRunner( |
| 173 | {base::MayBlock(), base::TaskPriority::USER_BLOCKING}); |
| 174 | std::vector<SignatureVerifier::SignatureAlgorithm> algorithms( |
| 175 | acceptable_algorithms.begin(), acceptable_algorithms.end()); |
| 176 | worker_task_runner->PostTaskAndReplyWithResult( |
| 177 | FROM_HERE, |
| 178 | base::BindOnce(&DoGenerateKey, std::move(algorithms), |
| 179 | MakeUnexportableKeyConfig(), lacontext_), |
| 180 | std::move(callback)); |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 181 | } |
| 182 | |
| 183 | void GetUserVerifyingSigningKey( |
| 184 | UserVerifyingKeyLabel key_label, |
Ken Buchanan | 0111d4e2 | 2024-07-11 19:53:53 | [diff] [blame] | 185 | UserVerifyingKeyCreationCallback callback) override { |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 186 | // Retrieving a key may result in disk access, so do it in a separate thread |
| 187 | // to avoid blocking the UI. |
| 188 | scoped_refptr<base::SequencedTaskRunner> worker_task_runner = |
| 189 | base::ThreadPool::CreateSequencedTaskRunner( |
| 190 | {base::MayBlock(), base::TaskPriority::USER_BLOCKING}); |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 191 | std::vector<uint8_t> wrapped_key(key_label.begin(), key_label.end()); |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 192 | worker_task_runner->PostTaskAndReplyWithResult( |
| 193 | FROM_HERE, |
| 194 | base::BindOnce(&DoGetKey, std::move(std::move(wrapped_key)), |
| 195 | MakeUnexportableKeyConfig(), lacontext_), |
| 196 | std::move(callback)); |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 197 | } |
| 198 | |
Nina Satragno | 9666abf | 2024-03-18 22:58:59 | [diff] [blame] | 199 | void DeleteUserVerifyingKey( |
| 200 | UserVerifyingKeyLabel key_label, |
| 201 | base::OnceCallback<void(bool)> callback) override { |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 202 | // Deleting a key may result in disk access, so do it in a separate thread |
| 203 | // to avoid blocking the UI. |
| 204 | scoped_refptr<base::SequencedTaskRunner> worker_task_runner = |
| 205 | base::ThreadPool::CreateSequencedTaskRunner( |
| 206 | {base::MayBlock(), base::TaskPriority::USER_BLOCKING}); |
Nina Satragno | 9666abf | 2024-03-18 22:58:59 | [diff] [blame] | 207 | std::vector<uint8_t> wrapped_key(key_label.begin(), key_label.end()); |
Nina Satragno | c804f56 | 2024-03-27 16:36:46 | [diff] [blame] | 208 | worker_task_runner->PostTaskAndReplyWithResult( |
| 209 | FROM_HERE, |
| 210 | base::BindOnce(&DoDeleteKey, std::move(wrapped_key), |
| 211 | MakeUnexportableKeyConfig()), |
| 212 | std::move(callback)); |
Nina Satragno | 9666abf | 2024-03-18 22:58:59 | [diff] [blame] | 213 | } |
| 214 | |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 215 | private: |
| 216 | UnexportableKeyProvider::Config MakeUnexportableKeyConfig() { |
| 217 | return { |
| 218 | .keychain_access_group = config_.keychain_access_group, |
Adam Langley | c98c481 | 2024-06-24 21:57:36 | [diff] [blame] | 219 | .access_control = |
| 220 | UnexportableKeyProvider::Config::AccessControl::kUserPresence, |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 221 | }; |
| 222 | } |
Nina Satragno | dae4492 | 2024-03-22 21:20:07 | [diff] [blame] | 223 | LAContext* __strong lacontext_; |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 224 | const UserVerifyingKeyProvider::Config config_; |
| 225 | }; |
| 226 | |
| 227 | } // namespace |
| 228 | |
| 229 | std::unique_ptr<UserVerifyingKeyProvider> GetUserVerifyingKeyProviderMac( |
| 230 | UserVerifyingKeyProvider::Config config) { |
| 231 | return std::make_unique<UserVerifyingKeyProviderMac>(std::move(config)); |
| 232 | } |
| 233 | |
| 234 | void AreMacUnexportableKeysAvailable(UserVerifyingKeyProvider::Config config, |
| 235 | base::OnceCallback<void(bool)> callback) { |
| 236 | if (!GetUnexportableKeyProvider( |
| 237 | {.keychain_access_group = std::move(config.keychain_access_group)})) { |
| 238 | std::move(callback).Run(false); |
| 239 | return; |
| 240 | } |
Nina Satragno | c7dc23a | 2024-03-20 17:19:22 | [diff] [blame] | 241 | std::move(callback).Run( |
Elly Fong-Jones | 38c29b6 | 2025-07-23 22:54:53 | [diff] [blame] | 242 | crypto::apple::KeychainV2::GetInstance().LAContextCanEvaluatePolicy( |
Nina Satragno | c7dc23a | 2024-03-20 17:19:22 | [diff] [blame] | 243 | LAPolicyDeviceOwnerAuthentication, /*error=*/nil)); |
Nina Satragno | 850d034 | 2024-03-15 18:16:17 | [diff] [blame] | 244 | } |
| 245 | |
| 246 | } // namespace crypto |