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 5#ifdef UNSAFE_BUFFERS_BUILD 6// TODO(crbug.com/351564777): Remove this and convert code to safer constructs. 7#pragma allow_unsafe_buffers 8#endif 9 10#include "crypto/unexportable_key.h" 11 12#import <CoreFoundation/CoreFoundation.h> 13#import <CryptoTokenKit/CryptoTokenKit.h> 14#import <Foundation/Foundation.h> 15#include <LocalAuthentication/LocalAuthentication.h> 16#import <Security/Security.h> 17 18#include <algorithm> 19#include <iterator> 20#include <memory> 21#include <string> 22#include <utility> 23#include <vector> 24 25#include "base/apple/bridging.h" 26#include "base/apple/foundation_util.h" 27#include "base/apple/scoped_cftyperef.h" 28#include "base/containers/contains.h" 29#include "base/containers/span.h" 30#include "base/logging.h" 31#include "base/memory/scoped_policy.h" 32#include "base/numerics/safe_conversions.h" 33#include "base/strings/sys_string_conversions.h" 34#include "base/threading/scoped_blocking_call.h" 35#include "crypto/apple_keychain_util.h" 36#include "crypto/apple_keychain_v2.h" 37#include "crypto/signature_verifier.h" 38#include "crypto/unexportable_key_mac.h" 39#include "third_party/boringssl/src/include/openssl/bn.h" 40#include "third_party/boringssl/src/include/openssl/bytestring.h" 41#include "third_party/boringssl/src/include/openssl/ec.h" 42#include "third_party/boringssl/src/include/openssl/evp.h" 43#include "third_party/boringssl/src/include/openssl/mem.h" 44#include "third_party/boringssl/src/include/openssl/obj.h" 45 46using base::apple::CFToNSPtrCast; 47using base::apple::NSToCFPtrCast; 48 49namespace crypto { 50 51namespace { 52 53// The size of an uncompressed x9.63 encoded EC public key, 04 || X || Y. 54constexpr size_t kUncompressedPointLength = 65; 55 56// The value of the kSecAttrLabel when generating the key. The documentation 57// claims this should be a user-visible label, but there does not exist any UI 58// that shows this value. Therefore, it is left untranslated. 59constexpr char kAttrLabel[] = "Chromium unexportable key"; 60 61// Copies a CFDataRef into a vector of bytes. 62std::vector<uint8_t> CFDataToVec(CFDataRef data) { 63 auto span = base::apple::CFDataToSpan(data); 64 return {span.begin(), span.end()}; 65} 66 67std::optional<std::vector<uint8_t>> Convertx963ToDerSpki( 68 base::span<const uint8_t> x962) { 69 // Parse x9.63 point into an |EC_POINT|. 70 bssl::UniquePtr<EC_GROUP> p256( 71 EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); 72 bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get())); 73 if (x962.size() != kUncompressedPointLength || 74 x962[0] != POINT_CONVERSION_UNCOMPRESSED || 75 !EC_POINT_oct2point(p256.get(), point.get(), x962.data(), x962.size(), 76 /*ctx=*/nullptr)) { 77 LOG(ERROR) << "P-256 public key is not on curve"; 78 return std::nullopt; 79 } 80 // Marshal point into a DER SPKI. 81 bssl::UniquePtr<EC_KEY> ec_key( 82 EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); 83 CHECK(EC_KEY_set_public_key(ec_key.get(), point.get())); 84 bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new()); 85 CHECK(EVP_PKEY_assign_EC_KEY(pkey.get(), ec_key.release())); 86 bssl::ScopedCBB cbb; 87 uint8_t* der_bytes = nullptr; 88 size_t der_bytes_len = 0; 89 CHECK(CBB_init(cbb.get(), /* initial size */ 128) && 90 EVP_marshal_public_key(cbb.get(), pkey.get()) && 91 CBB_finish(cbb.get(), &der_bytes, &der_bytes_len)); 92 std::vector<uint8_t> ret(der_bytes, der_bytes + der_bytes_len); 93 OPENSSL_free(der_bytes); 94 return ret; 95} 96 97// UnexportableSigningKeyMac is an implementation of the UnexportableSigningKey 98// interface on top of Apple's Secure Enclave. 99class UnexportableSigningKeyMac : public UnexportableSigningKey { 100 public: 101 UnexportableSigningKeyMac(base::apple::ScopedCFTypeRef<SecKeyRef> key, 102 CFDictionaryRef key_attributes) 103 : key_(std::move(key)), 104 application_label_( 105 CFDataToVec(base::apple::GetValueFromDictionary<CFDataRef>( 106 key_attributes, 107 kSecAttrApplicationLabel))) { 108 base::apple::ScopedCFTypeRef<SecKeyRef> public_key( 109 AppleKeychainV2::GetInstance().KeyCopyPublicKey(key_.get())); 110 base::apple::ScopedCFTypeRef<CFDataRef> x962_bytes( 111 AppleKeychainV2::GetInstance().KeyCopyExternalRepresentation( 112 public_key.get(), /*error=*/nil)); 113 CHECK(x962_bytes); 114 base::span<const uint8_t> x962_span = 115 base::apple::CFDataToSpan(x962_bytes.get()); 116 public_key_spki_ = *Convertx963ToDerSpki(x962_span); 117 } 118 119 ~UnexportableSigningKeyMac() override = default; 120 121 SignatureVerifier::SignatureAlgorithm Algorithm() const override { 122 return SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256; 123 } 124 125 std::vector<uint8_t> GetSubjectPublicKeyInfo() const override { 126 return public_key_spki_; 127 } 128 129 std::vector<uint8_t> GetWrappedKey() const override { 130 return application_label_; 131 } 132 133 std::optional<std::vector<uint8_t>> SignSlowly( 134 base::span<const uint8_t> data) override { 135 SecKeyAlgorithm algorithm = kSecKeyAlgorithmECDSASignatureMessageX962SHA256; 136 if (!SecKeyIsAlgorithmSupported(key_.get(), kSecKeyOperationTypeSign, 137 algorithm)) { 138 // This is not expected to happen, but it could happen if e.g. the key had 139 // been replaced by a key of a different type with the same label. 140 LOG(ERROR) << "Key does not support ECDSA algorithm"; 141 return std::nullopt; 142 } 143 144 NSData* nsdata = [NSData dataWithBytes:data.data() length:data.size()]; 145 base::apple::ScopedCFTypeRef<CFErrorRef> error; 146 base::apple::ScopedCFTypeRef<CFDataRef> signature( 147 AppleKeychainV2::GetInstance().KeyCreateSignature( 148 key_.get(), algorithm, NSToCFPtrCast(nsdata), 149 error.InitializeInto())); 150 if (!signature) { 151 LOG(ERROR) << "Error signing with key: " << error.get(); 152 return std::nullopt; 153 } 154 return CFDataToVec(signature.get()); 155 } 156 157 bool IsHardwareBacked() const override { return true; } 158 159 SecKeyRef GetSecKeyRef() const override { return key_.get(); } 160 161 private: 162 // The wrapped key as returned by the Keychain API. 163 const base::apple::ScopedCFTypeRef<SecKeyRef> key_; 164 165 // The MacOS Keychain API sets the application label to the hash of the public 166 // key. We use this to uniquely identify the key in lieu of a wrapped private 167 // key. 168 const std::vector<uint8_t> application_label_; 169 170 // The public key in DER SPKI format. 171 std::vector<uint8_t> public_key_spki_; 172}; 173 174} // namespace 175 176struct UnexportableKeyProviderMac::ObjCStorage { 177 NSString* __strong keychain_access_group_; 178 NSString* __strong application_tag_; 179}; 180 181UnexportableKeyProviderMac::UnexportableKeyProviderMac(Config config) 182 : access_control_(config.access_control), 183 objc_storage_(std::make_unique<ObjCStorage>()) { 184 objc_storage_->keychain_access_group_ = 185 base::SysUTF8ToNSString(std::move(config.keychain_access_group)); 186 objc_storage_->application_tag_ = 187 base::SysUTF8ToNSString(std::move(config.application_tag)); 188} 189UnexportableKeyProviderMac::~UnexportableKeyProviderMac() = default; 190 191std::optional<SignatureVerifier::SignatureAlgorithm> 192UnexportableKeyProviderMac::SelectAlgorithm( 193 base::span<const SignatureVerifier::SignatureAlgorithm> 194 acceptable_algorithms) { 195 return base::Contains(acceptable_algorithms, SignatureVerifier::ECDSA_SHA256) 196 ? std::make_optional(SignatureVerifier::ECDSA_SHA256) 197 : std::nullopt; 198} 199 200std::unique_ptr<UnexportableSigningKey> 201UnexportableKeyProviderMac::GenerateSigningKeySlowly( 202 base::span<const SignatureVerifier::SignatureAlgorithm> 203 acceptable_algorithms) { 204 return GenerateSigningKeySlowly(acceptable_algorithms, /*lacontext=*/nil); 205} 206 207std::unique_ptr<UnexportableSigningKey> 208UnexportableKeyProviderMac::GenerateSigningKeySlowly( 209 base::span<const SignatureVerifier::SignatureAlgorithm> 210 acceptable_algorithms, 211 LAContext* lacontext) { 212 // The Secure Enclave only supports elliptic curve keys. 213 if (!SelectAlgorithm(acceptable_algorithms)) { 214 return nullptr; 215 } 216 217 // Generate the key pair. 218 SecAccessControlCreateFlags control_flags = kSecAccessControlPrivateKeyUsage; 219 switch (access_control_) { 220 case UnexportableKeyProvider::Config::AccessControl::kUserPresence: 221 // kSecAccessControlUserPresence is documented[1] (at the time of 222 // writing) to be "equivalent to specifying kSecAccessControlBiometryAny, 223 // kSecAccessControlOr, and kSecAccessControlDevicePasscode". This is 224 // incorrect because includingkSecAccessControlBiometryAny causes key 225 // creation to fail if biometrics are supported but not enrolled. It also 226 // appears to support Apple Watch confirmation, but this isn't documented 227 // (and kSecAccessControlWatch is deprecated as of macOS 15). 228 // 229 // Reported as FB14040169. 230 // 231 // [1] https://developer.apple.com/documentation/security/ 232 // secaccesscontrolcreateflags/ksecaccesscontroluserpresence 233 control_flags |= kSecAccessControlUserPresence; 234 break; 235 case UnexportableKeyProvider::Config::AccessControl::kNone: 236 // No additional flag. 237 break; 238 } 239 base::apple::ScopedCFTypeRef<SecAccessControlRef> access( 240 SecAccessControlCreateWithFlags( 241 kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, 242 control_flags, 243 /*error=*/nil)); 244 CHECK(access); 245 246 NSMutableDictionary* key_attributes = 247 [NSMutableDictionary dictionaryWithDictionary:@{ 248 CFToNSPtrCast(kSecAttrIsPermanent) : @YES, 249 CFToNSPtrCast(kSecAttrAccessControl) : (__bridge id)access.get(), 250 }]; 251 if (lacontext) { 252 key_attributes[CFToNSPtrCast(kSecUseAuthenticationContext)] = lacontext; 253 } 254 255 NSDictionary* attributes = @{ 256 CFToNSPtrCast(kSecUseDataProtectionKeychain) : @YES, 257 CFToNSPtrCast(kSecAttrKeyType) : 258 CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom), 259 CFToNSPtrCast(kSecAttrKeySizeInBits) : @256, 260 CFToNSPtrCast(kSecAttrTokenID) : 261 CFToNSPtrCast(kSecAttrTokenIDSecureEnclave), 262 CFToNSPtrCast(kSecPrivateKeyAttrs) : key_attributes, 263 CFToNSPtrCast(kSecAttrAccessGroup) : objc_storage_->keychain_access_group_, 264 CFToNSPtrCast(kSecAttrLabel) : base::SysUTF8ToNSString(kAttrLabel), 265 CFToNSPtrCast(kSecAttrApplicationTag) : objc_storage_->application_tag_, 266 }; 267 268 base::apple::ScopedCFTypeRef<CFErrorRef> error; 269 base::apple::ScopedCFTypeRef<SecKeyRef> private_key( 270 AppleKeychainV2::GetInstance().KeyCreateRandomKey( 271 NSToCFPtrCast(attributes), error.InitializeInto())); 272 if (!private_key) { 273 LOG(ERROR) << "Could not create private key: " << error.get(); 274 return nullptr; 275 } 276 base::apple::ScopedCFTypeRef<CFDictionaryRef> key_metadata = 277 AppleKeychainV2::GetInstance().KeyCopyAttributes(private_key.get()); 278 return std::make_unique<UnexportableSigningKeyMac>(std::move(private_key), 279 key_metadata.get()); 280} 281 282std::unique_ptr<UnexportableSigningKey> 283UnexportableKeyProviderMac::FromWrappedSigningKeySlowly( 284 base::span<const uint8_t> wrapped_key) { 285 return FromWrappedSigningKeySlowly(wrapped_key, /*lacontext=*/nil); 286} 287 288std::unique_ptr<UnexportableSigningKey> 289UnexportableKeyProviderMac::FromWrappedSigningKeySlowly( 290 base::span<const uint8_t> wrapped_key, 291 LAContext* lacontext) { 292 base::apple::ScopedCFTypeRef<CFTypeRef> key_data; 293 294 NSMutableDictionary* query = [NSMutableDictionary dictionaryWithDictionary:@{ 295 CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey), 296 CFToNSPtrCast(kSecAttrKeyType) : 297 CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom), 298 CFToNSPtrCast(kSecReturnRef) : @YES, 299 CFToNSPtrCast(kSecReturnAttributes) : @YES, 300 CFToNSPtrCast(kSecAttrAccessGroup) : objc_storage_->keychain_access_group_, 301 CFToNSPtrCast(kSecAttrApplicationLabel) : 302 [NSData dataWithBytes:wrapped_key.data() length:wrapped_key.size()], 303 }]; 304 if (lacontext) { 305 query[CFToNSPtrCast(kSecUseAuthenticationContext)] = lacontext; 306 } 307 AppleKeychainV2::GetInstance().ItemCopyMatching(NSToCFPtrCast(query), 308 key_data.InitializeInto()); 309 CFDictionaryRef key_attributes = 310 base::apple::CFCast<CFDictionaryRef>(key_data.get()); 311 if (!key_attributes) { 312 return nullptr; 313 } 314 base::apple::ScopedCFTypeRef<SecKeyRef> key( 315 base::apple::GetValueFromDictionary<SecKeyRef>(key_attributes, 316 kSecValueRef), 317 base::scoped_policy::RETAIN); 318 return std::make_unique<UnexportableSigningKeyMac>(std::move(key), 319 key_attributes); 320} 321 322bool UnexportableKeyProviderMac::DeleteSigningKeySlowly( 323 base::span<const uint8_t> wrapped_key) { 324 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, 325 base::BlockingType::WILL_BLOCK); 326 NSDictionary* query = @{ 327 CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey), 328 CFToNSPtrCast(kSecAttrKeyType) : 329 CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom), 330 CFToNSPtrCast(kSecAttrAccessGroup) : objc_storage_->keychain_access_group_, 331 CFToNSPtrCast(kSecAttrApplicationLabel) : 332 [NSData dataWithBytes:wrapped_key.data() length:wrapped_key.size()], 333 }; 334 OSStatus result = 335 AppleKeychainV2::GetInstance().ItemDelete(NSToCFPtrCast(query)); 336 return result == errSecSuccess; 337} 338 339std::unique_ptr<UnexportableKeyProviderMac> GetUnexportableKeyProviderMac( 340 UnexportableKeyProvider::Config config) { 341 CHECK(!config.keychain_access_group.empty()) 342 << "A keychain access group must be set when using unexportable keys on " 343 "macOS"; 344 if (![AppleKeychainV2::GetInstance().GetTokenIDs() 345 containsObject:CFToNSPtrCast(kSecAttrTokenIDSecureEnclave)]) { 346 return nullptr; 347 } 348 // Inspecting the binary for the entitlement is not available on iOS, assume 349 // it is available. 350#if !BUILDFLAG(IS_IOS) 351 if (!ExecutableHasKeychainAccessGroupEntitlement( 352 config.keychain_access_group)) { 353 return nullptr; 354 } 355#endif // !BUILDFLAG(IS_IOS) 356 return std::make_unique<UnexportableKeyProviderMac>(std::move(config)); 357} 358 359} // namespace crypto 360