• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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