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#include <algorithm> 6#include <iterator> 7#include <memory> 8#include <string> 9#include <utility> 10#include <vector> 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 "base/apple/bridging.h" 19#include "base/apple/foundation_util.h" 20#include "base/apple/scoped_cftyperef.h" 21#include "base/containers/contains.h" 22#include "base/containers/span.h" 23#include "base/feature_list.h" 24#include "base/logging.h" 25#include "base/memory/scoped_policy.h" 26#include "base/numerics/safe_conversions.h" 27#include "base/strings/sys_string_conversions.h" 28#include "crypto/apple_keychain_util.h" 29#include "crypto/apple_keychain_v2.h" 30#include "crypto/features.h" 31#include "crypto/signature_verifier.h" 32#include "crypto/unexportable_key.h" 33#include "crypto/unexportable_key_mac.h" 34#include "third_party/boringssl/src/include/openssl/bn.h" 35#include "third_party/boringssl/src/include/openssl/bytestring.h" 36#include "third_party/boringssl/src/include/openssl/ec.h" 37#include "third_party/boringssl/src/include/openssl/evp.h" 38#include "third_party/boringssl/src/include/openssl/mem.h" 39#include "third_party/boringssl/src/include/openssl/obj.h" 40 41using base::apple::CFToNSPtrCast; 42using base::apple::NSToCFPtrCast; 43 44namespace crypto { 45 46namespace { 47 48// The size of an uncompressed x9.63 encoded EC public key, 04 || X || Y. 49constexpr size_t kUncompressedPointLength = 65; 50 51// The value of the kSecAttrLabel when generating the key. The documentation 52// claims this should be a user-visible label, but there does not exist any UI 53// that shows this value. Therefore, it is left untranslated. 54constexpr char kAttrLabel[] = "Chromium unexportable key"; 55 56// Returns a span of a CFDataRef. 57base::span<const uint8_t> ToSpan(CFDataRef data) { 58 return base::make_span(CFDataGetBytePtr(data), 59 base::checked_cast<size_t>(CFDataGetLength(data))); 60} 61 62// Copies a CFDataRef into a vector of bytes. 63std::vector<uint8_t> CFDataToVec(CFDataRef data) { 64 base::span<const uint8_t> span = ToSpan(data); 65 return std::vector<uint8_t>(span.begin(), span.end()); 66} 67 68std::optional<std::vector<uint8_t>> Convertx963ToDerSpki( 69 base::span<const uint8_t> x962) { 70 // Parse x9.63 point into an |EC_POINT|. 71 bssl::UniquePtr<EC_GROUP> p256( 72 EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); 73 bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get())); 74 if (x962.size() != kUncompressedPointLength || 75 x962[0] != POINT_CONVERSION_UNCOMPRESSED || 76 !EC_POINT_oct2point(p256.get(), point.get(), x962.data(), x962.size(), 77 /*ctx=*/nullptr)) { 78 LOG(ERROR) << "P-256 public key is not on curve"; 79 return std::nullopt; 80 } 81 // Marshal point into a DER SPKI. 82 bssl::UniquePtr<EC_KEY> ec_key( 83 EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); 84 CHECK(EC_KEY_set_public_key(ec_key.get(), point.get())); 85 bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new()); 86 CHECK(EVP_PKEY_assign_EC_KEY(pkey.get(), ec_key.release())); 87 bssl::ScopedCBB cbb; 88 uint8_t* der_bytes = nullptr; 89 size_t der_bytes_len = 0; 90 CHECK(CBB_init(cbb.get(), /* initial size */ 128) && 91 EVP_marshal_public_key(cbb.get(), pkey.get()) && 92 CBB_finish(cbb.get(), &der_bytes, &der_bytes_len)); 93 std::vector<uint8_t> ret(der_bytes, der_bytes + der_bytes_len); 94 OPENSSL_free(der_bytes); 95 return ret; 96} 97 98// UnexportableSigningKeyMac is an implementation of the UnexportableSigningKey 99// interface on top of Apple's Secure Enclave. 100class UnexportableSigningKeyMac : public UnexportableSigningKey { 101 public: 102 UnexportableSigningKeyMac(base::apple::ScopedCFTypeRef<SecKeyRef> key, 103 CFDictionaryRef key_attributes) 104 : key_(std::move(key)), 105 application_label_( 106 CFDataToVec(base::apple::GetValueFromDictionary<CFDataRef>( 107 key_attributes, 108 kSecAttrApplicationLabel))) { 109 base::apple::ScopedCFTypeRef<SecKeyRef> public_key( 110 AppleKeychainV2::GetInstance().KeyCopyPublicKey(key_.get())); 111 base::apple::ScopedCFTypeRef<CFDataRef> x962_bytes( 112 AppleKeychainV2::GetInstance().KeyCopyExternalRepresentation( 113 public_key.get(), /*error=*/nil)); 114 CHECK(x962_bytes); 115 base::span<const uint8_t> x962_span = ToSpan(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 SecKeyRef GetSecKeyRef() const override { return key_.get(); } 158 159 private: 160 // The wrapped key as returned by the Keychain API. 161 const base::apple::ScopedCFTypeRef<SecKeyRef> key_; 162 163 // The MacOS Keychain API sets the application label to the hash of the public 164 // key. We use this to uniquely identify the key in lieu of a wrapped private 165 // key. 166 const std::vector<uint8_t> application_label_; 167 168 // The public key in DER SPKI format. 169 std::vector<uint8_t> public_key_spki_; 170}; 171 172} // namespace 173 174struct UnexportableKeyProviderMac::ObjCStorage { 175 NSString* __strong keychain_access_group_; 176 NSString* __strong application_tag_; 177}; 178 179UnexportableKeyProviderMac::UnexportableKeyProviderMac(Config config) 180 : access_control_(config.access_control), 181 objc_storage_(std::make_unique<ObjCStorage>()) { 182 objc_storage_->keychain_access_group_ = 183 base::SysUTF8ToNSString(std::move(config.keychain_access_group)); 184 objc_storage_->application_tag_ = 185 base::SysUTF8ToNSString(std::move(config.application_tag)); 186} 187UnexportableKeyProviderMac::~UnexportableKeyProviderMac() = default; 188 189std::optional<SignatureVerifier::SignatureAlgorithm> 190UnexportableKeyProviderMac::SelectAlgorithm( 191 base::span<const SignatureVerifier::SignatureAlgorithm> 192 acceptable_algorithms) { 193 return base::Contains(acceptable_algorithms, SignatureVerifier::ECDSA_SHA256) 194 ? std::make_optional(SignatureVerifier::ECDSA_SHA256) 195 : std::nullopt; 196} 197 198std::unique_ptr<UnexportableSigningKey> 199UnexportableKeyProviderMac::GenerateSigningKeySlowly( 200 base::span<const SignatureVerifier::SignatureAlgorithm> 201 acceptable_algorithms) { 202 return GenerateSigningKeySlowly(acceptable_algorithms, /*lacontext=*/nil); 203} 204 205std::unique_ptr<UnexportableSigningKey> 206UnexportableKeyProviderMac::GenerateSigningKeySlowly( 207 base::span<const SignatureVerifier::SignatureAlgorithm> 208 acceptable_algorithms, 209 LAContext* lacontext) { 210 // The Secure Enclave only supports elliptic curve keys. 211 if (!SelectAlgorithm(acceptable_algorithms)) { 212 return nullptr; 213 } 214 215 // Generate the key pair. 216 SecAccessControlCreateFlags control_flags = kSecAccessControlPrivateKeyUsage; 217 switch (access_control_) { 218 case UnexportableKeyProvider::Config::AccessControl::kUserPresence: 219 control_flags |= kSecAccessControlUserPresence; 220 break; 221 case UnexportableKeyProvider::Config::AccessControl::kNone: 222 // No additional flag. 223 break; 224 } 225 base::apple::ScopedCFTypeRef<SecAccessControlRef> access( 226 SecAccessControlCreateWithFlags( 227 kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, 228 control_flags, 229 /*error=*/nil)); 230 231 NSMutableDictionary* key_attributes = 232 [NSMutableDictionary dictionaryWithDictionary:@{ 233 CFToNSPtrCast(kSecAttrIsPermanent) : @YES, 234 CFToNSPtrCast(kSecAttrAccessControl) : (__bridge id)access.get(), 235 }]; 236 if (lacontext) { 237 key_attributes[CFToNSPtrCast(kSecUseAuthenticationContext)] = lacontext; 238 } 239 240 NSDictionary* attributes = @{ 241 CFToNSPtrCast(kSecUseDataProtectionKeychain) : @YES, 242 CFToNSPtrCast(kSecAttrKeyType) : 243 CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom), 244 CFToNSPtrCast(kSecAttrKeySizeInBits) : @256, 245 CFToNSPtrCast(kSecAttrTokenID) : 246 CFToNSPtrCast(kSecAttrTokenIDSecureEnclave), 247 CFToNSPtrCast(kSecPrivateKeyAttrs) : key_attributes, 248 CFToNSPtrCast(kSecAttrAccessGroup) : objc_storage_->keychain_access_group_, 249 CFToNSPtrCast(kSecAttrLabel) : base::SysUTF8ToNSString(kAttrLabel), 250 CFToNSPtrCast(kSecAttrApplicationTag) : objc_storage_->application_tag_, 251 }; 252 253 base::apple::ScopedCFTypeRef<CFErrorRef> error; 254 base::apple::ScopedCFTypeRef<SecKeyRef> private_key( 255 AppleKeychainV2::GetInstance().KeyCreateRandomKey( 256 NSToCFPtrCast(attributes), error.InitializeInto())); 257 if (!private_key) { 258 LOG(ERROR) << "Could not create private key: " << error.get(); 259 return nullptr; 260 } 261 base::apple::ScopedCFTypeRef<CFDictionaryRef> key_metadata = 262 AppleKeychainV2::GetInstance().KeyCopyAttributes(private_key.get()); 263 return std::make_unique<UnexportableSigningKeyMac>(std::move(private_key), 264 key_metadata.get()); 265} 266 267std::unique_ptr<UnexportableSigningKey> 268UnexportableKeyProviderMac::FromWrappedSigningKeySlowly( 269 base::span<const uint8_t> wrapped_key) { 270 return FromWrappedSigningKeySlowly(wrapped_key, /*lacontext=*/nil); 271} 272 273std::unique_ptr<UnexportableSigningKey> 274UnexportableKeyProviderMac::FromWrappedSigningKeySlowly( 275 base::span<const uint8_t> wrapped_key, 276 LAContext* lacontext) { 277 base::apple::ScopedCFTypeRef<CFTypeRef> key_data; 278 279 NSMutableDictionary* query = [NSMutableDictionary dictionaryWithDictionary:@{ 280 CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey), 281 CFToNSPtrCast(kSecAttrKeyType) : 282 CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom), 283 CFToNSPtrCast(kSecReturnRef) : @YES, 284 CFToNSPtrCast(kSecReturnAttributes) : @YES, 285 CFToNSPtrCast(kSecAttrAccessGroup) : objc_storage_->keychain_access_group_, 286 CFToNSPtrCast(kSecAttrApplicationLabel) : 287 [NSData dataWithBytes:wrapped_key.data() length:wrapped_key.size()], 288 }]; 289 if (lacontext) { 290 query[CFToNSPtrCast(kSecUseAuthenticationContext)] = lacontext; 291 } 292 AppleKeychainV2::GetInstance().ItemCopyMatching(NSToCFPtrCast(query), 293 key_data.InitializeInto()); 294 CFDictionaryRef key_attributes = 295 base::apple::CFCast<CFDictionaryRef>(key_data.get()); 296 if (!key_attributes) { 297 return nullptr; 298 } 299 base::apple::ScopedCFTypeRef<SecKeyRef> key( 300 base::apple::GetValueFromDictionary<SecKeyRef>(key_attributes, 301 kSecValueRef), 302 base::scoped_policy::RETAIN); 303 return std::make_unique<UnexportableSigningKeyMac>(std::move(key), 304 key_attributes); 305} 306 307bool UnexportableKeyProviderMac::DeleteSigningKey( 308 base::span<const uint8_t> wrapped_key) { 309 NSDictionary* query = @{ 310 CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey), 311 CFToNSPtrCast(kSecAttrKeyType) : 312 CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom), 313 CFToNSPtrCast(kSecAttrAccessGroup) : objc_storage_->keychain_access_group_, 314 CFToNSPtrCast(kSecAttrApplicationLabel) : 315 [NSData dataWithBytes:wrapped_key.data() length:wrapped_key.size()], 316 }; 317 OSStatus result = 318 AppleKeychainV2::GetInstance().ItemDelete(NSToCFPtrCast(query)); 319 return result == errSecSuccess; 320} 321 322std::unique_ptr<UnexportableKeyProviderMac> GetUnexportableKeyProviderMac( 323 UnexportableKeyProvider::Config config) { 324 if (!base::FeatureList::IsEnabled(crypto::kEnableMacUnexportableKeys)) { 325 return nullptr; 326 } 327 CHECK(!config.keychain_access_group.empty()) 328 << "A keychain access group must be set when using unexportable keys on " 329 "macOS"; 330 if (![AppleKeychainV2::GetInstance().GetTokenIDs() 331 containsObject:CFToNSPtrCast(kSecAttrTokenIDSecureEnclave)]) { 332 return nullptr; 333 } 334 // Inspecting the binary for the entitlement is not available on iOS, assume 335 // it is available. 336#if !BUILDFLAG(IS_IOS) 337 if (!ExecutableHasKeychainAccessGroupEntitlement( 338 config.keychain_access_group)) { 339 LOG(ERROR) << "Unexportable keys unavailable because keychain-access-group " 340 "entitlement missing or incorrect. Expected value: " 341 << config.keychain_access_group; 342 return nullptr; 343 } 344#endif // !BUILDFLAG(IS_IOS) 345 return std::make_unique<UnexportableKeyProviderMac>(std::move(config)); 346} 347 348} // namespace crypto 349