chromium/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.h"

#include <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#include <Security/Security.h>

#include <string>
#include <vector>

#include "base/apple/bridging.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/containers/span.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/metrics_util.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_helper.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/shared_command_constants.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"

using base::apple::CFToNSPtrCast;
using base::apple::NSToCFPtrCast;

namespace enterprise_connectors {

namespace {

bool IsSuccess(OSStatus status) {
  return status == errSecSuccess;
}

// Creates and returns the secure enclave private key attributes used
// for key creation. These key attributes represent the key created in
// the permanent key location.
NSDictionary* CreateAttributesForKey() {
  base::apple::ScopedCFTypeRef<SecAccessControlRef> access_control(
      SecAccessControlCreateWithFlags(
          kCFAllocatorDefault,
          // Private key can only be used if the device was unlocked at least
          // once.
          kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
          // Private key is available for signing.
          kSecAccessControlPrivateKeyUsage, /*error=*/nullptr));

  NSDictionary* private_key_params = @{
    CFToNSPtrCast(kSecAttrIsPermanent) : @YES,
    CFToNSPtrCast(kSecAttrAccessControl) : (__bridge id)access_control.get(),
  };

  NSDictionary* attributes = @{
    CFToNSPtrCast(kSecAttrAccessGroup) :
        base::SysUTF8ToNSString(constants::kKeychainAccessGroup),
    CFToNSPtrCast(kSecAttrKeyType) :
        CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom),
    CFToNSPtrCast(kSecAttrTokenID) :
        CFToNSPtrCast(kSecAttrTokenIDSecureEnclave),
    CFToNSPtrCast(kSecAttrKeySizeInBits) : @256,
    CFToNSPtrCast(kSecAttrLabel) :
        base::SysUTF8ToNSString(constants::kDeviceTrustSigningKeyLabel),
    CFToNSPtrCast(kSecPrivateKeyAttrs) : private_key_params,
  };

  return attributes;
}

// Creates the query used for querying the keychain for the secure key
// reference.
NSDictionary* CreateQueryForKey(SecureEnclaveClient::KeyType type) {
  return @{
    CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey),
    CFToNSPtrCast(kSecAttrKeyType) :
        CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom),
    CFToNSPtrCast(kSecAttrLabel) :
        base::SysUTF8ToNSString(SecureEnclaveClient::GetLabelFromKeyType(type)),
    CFToNSPtrCast(kSecReturnRef) : @YES,
    CFToNSPtrCast(kSecUseDataProtectionKeychain) : @YES,
  };
}

// Converts an external representation of an EC public key from ANSI X9.63
// standard (using a byte string of 04 || X || Y) to a DER-encoded SPKI
// structure.
bool ConvertPublicKey(CFDataRef data_ref, std::vector<uint8_t>& output) {
  if (!data_ref) {
    return false;
  }

  auto data =
      base::make_span(CFDataGetBytePtr(data_ref),
                      base::checked_cast<size_t>(CFDataGetLength(data_ref)));
  bssl::UniquePtr<EC_GROUP> p256(
      EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
  bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
  if (!EC_POINT_oct2point(p256.get(), point.get(), data.data(), data.size(),
                          /*ctx=*/nullptr)) {
    return false;
  }
  bssl::UniquePtr<EC_KEY> ec_key(
      EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
  EC_KEY_set_public_key(ec_key.get(), point.get());
  bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
  EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.get());

  uint8_t* der;
  size_t der_len;
  bssl::ScopedCBB cbb;
  if (!CBB_init(cbb.get(), 0) ||
      !EVP_marshal_public_key(cbb.get(), pkey.get()) ||
      !CBB_finish(cbb.get(), &der, &der_len)) {
    return false;
  }

  output.assign(der, der + der_len);
  bssl::UniquePtr<uint8_t> delete_signed_cert_bytes(der);
  return true;
}

}  // namespace

SecureEnclaveClientImpl::SecureEnclaveClientImpl()
    : helper_(SecureEnclaveHelper::Create()) {
  DCHECK(helper_);
}

SecureEnclaveClientImpl::~SecureEnclaveClientImpl() = default;

base::apple::ScopedCFTypeRef<SecKeyRef>
SecureEnclaveClientImpl::CreatePermanentKey() {
  NSDictionary* attributes = CreateAttributesForKey();
  if (!attributes)
    return base::apple::ScopedCFTypeRef<SecKeyRef>();

  // Deletes a permanent Secure Enclave key if it exists from a previous
  // key rotation.
  DeleteKey(KeyType::kPermanent);

  OSStatus status;
  auto key = helper_->CreateSecureKey(NSToCFPtrCast(attributes), &status);
  if (!key) {
    RecordKeyOperationStatus(KeychainOperation::kCreate, KeyType::kPermanent,
                             status);
  }

  return key;
}

base::apple::ScopedCFTypeRef<SecKeyRef> SecureEnclaveClientImpl::CopyStoredKey(
    KeyType type,
    OSStatus* error) {
  OSStatus status;
  auto key_ref =
      helper_->CopyKey(NSToCFPtrCast(CreateQueryForKey(type)), &status);
  if (!key_ref) {
    RecordKeyOperationStatus(KeychainOperation::kCopy, type, status);
    if (error) {
      *error = status;
    }
  }

  return key_ref;
}

bool SecureEnclaveClientImpl::UpdateStoredKeyLabel(KeyType current_key_type,
                                                   KeyType new_key_type) {
  // Deletes the `new_key_type` label if it exists in the keychain.
  DeleteKey(new_key_type);

  auto label = SecureEnclaveClient::GetLabelFromKeyType(new_key_type);
  if (label.empty())
    return false;

  NSDictionary* attributes_to_update =
      @{CFToNSPtrCast(kSecAttrLabel) : base::SysUTF8ToNSString(label)};

  OSStatus status =
      helper_->Update(NSToCFPtrCast(CreateQueryForKey(current_key_type)),
                      NSToCFPtrCast(attributes_to_update));

  bool success = IsSuccess(status);
  if (!success) {
    RecordKeyOperationStatus(KeychainOperation::kUpdate, current_key_type,
                             status);
  }

  return success;
}

bool SecureEnclaveClientImpl::DeleteKey(KeyType type) {
  OSStatus status = helper_->Delete(NSToCFPtrCast(CreateQueryForKey(type)));

  bool success = IsSuccess(status);
  if (!success) {
    RecordKeyOperationStatus(KeychainOperation::kDelete, type, status);
  }

  return success;
}

bool SecureEnclaveClientImpl::ExportPublicKey(SecKeyRef key,
                                              std::vector<uint8_t>& output,
                                              OSStatus* error) {
  base::apple::ScopedCFTypeRef<SecKeyRef> public_key(SecKeyCopyPublicKey(key));
  if (!public_key) {
    if (error) {
      // The API doesn't return any OSStatus, but we'll use errSecInvalidItemRef
      // for tracking purposes.
      *error = errSecInvalidItemRef;
    }
    return false;
  }

  base::apple::ScopedCFTypeRef<CFErrorRef> error_ref;
  base::apple::ScopedCFTypeRef<CFDataRef> data_ref(
      SecKeyCopyExternalRepresentation(public_key.get(),
                                       error_ref.InitializeInto()));

  if (!data_ref) {
    if (error) {
      // In the odd chance that the API did not populate `error_ref`, fallback
      // to errSecCoreFoundationUnknown.
      *error = error_ref ? CFErrorGetCode(error_ref.get())
                         : errSecCoreFoundationUnknown;
    }
    return false;
  }

  if (!ConvertPublicKey(data_ref.get(), output)) {
    if (error) {
      // This arithmetic function doesn't really interact with any OS API, but
      // we'll use errSecConversionError for tracking purposes.
      *error = errSecConversionError;
    }
    return false;
  }
  return true;
}

bool SecureEnclaveClientImpl::SignDataWithKey(SecKeyRef key,
                                              base::span<const uint8_t> data,
                                              std::vector<uint8_t>& output,
                                              OSStatus* error) {
  base::apple::ScopedCFTypeRef<CFDataRef> data_ref(
      CFDataCreate(kCFAllocatorDefault, data.data(),
                   base::checked_cast<CFIndex>(data.size())));

  base::apple::ScopedCFTypeRef<CFErrorRef> error_ref;
  base::apple::ScopedCFTypeRef<CFDataRef> signature(SecKeyCreateSignature(
      key, kSecKeyAlgorithmECDSASignatureMessageX962SHA256, data_ref.get(),
      error_ref.InitializeInto()));

  if (!signature) {
    if (error) {
      // In the odd chance that the API did not populate `error_ref`, fallback
      // to errSecCoreFoundationUnknown.
      *error = error_ref ? CFErrorGetCode(error_ref.get())
                         : errSecCoreFoundationUnknown;
    }
    return false;
  }

  output.assign(
      CFDataGetBytePtr(signature.get()),
      CFDataGetBytePtr(signature.get()) + CFDataGetLength(signature.get()));
  return true;
}

bool SecureEnclaveClientImpl::VerifySecureEnclaveSupported() {
  return helper_->IsSecureEnclaveSupported();
}

}  // namespace enterprise_connectors