xref: /aosp_15_r20/external/cronet/crypto/unexportable_key_mac.mm (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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