mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2025-02-26 20:03:26 +03:00
370 lines
14 KiB
Plaintext
370 lines
14 KiB
Plaintext
// Copyright 2024 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/351564777): Remove this and convert code to safer constructs.
|
|
#pragma allow_unsafe_buffers
|
|
#endif
|
|
|
|
#include "crypto/unexportable_key.h"
|
|
|
|
#import <CoreFoundation/CoreFoundation.h>
|
|
#import <CryptoTokenKit/CryptoTokenKit.h>
|
|
#import <Foundation/Foundation.h>
|
|
#include <LocalAuthentication/LocalAuthentication.h>
|
|
#import <Security/Security.h>
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "base/apple/bridging.h"
|
|
#include "base/apple/foundation_util.h"
|
|
#include "base/apple/scoped_cftyperef.h"
|
|
#include "base/containers/contains.h"
|
|
#include "base/containers/span.h"
|
|
#include "base/feature_list.h"
|
|
#include "base/logging.h"
|
|
#include "base/memory/scoped_policy.h"
|
|
#include "base/numerics/safe_conversions.h"
|
|
#include "base/strings/sys_string_conversions.h"
|
|
#include "base/threading/scoped_blocking_call.h"
|
|
#include "crypto/apple_keychain_util.h"
|
|
#include "crypto/apple_keychain_v2.h"
|
|
#include "crypto/features.h"
|
|
#include "crypto/signature_verifier.h"
|
|
#include "crypto/unexportable_key_mac.h"
|
|
#include "third_party/boringssl/src/include/openssl/bn.h"
|
|
#include "third_party/boringssl/src/include/openssl/bytestring.h"
|
|
#include "third_party/boringssl/src/include/openssl/ec.h"
|
|
#include "third_party/boringssl/src/include/openssl/evp.h"
|
|
#include "third_party/boringssl/src/include/openssl/mem.h"
|
|
#include "third_party/boringssl/src/include/openssl/obj.h"
|
|
|
|
using base::apple::CFToNSPtrCast;
|
|
using base::apple::NSToCFPtrCast;
|
|
|
|
namespace crypto {
|
|
|
|
namespace {
|
|
|
|
// The size of an uncompressed x9.63 encoded EC public key, 04 || X || Y.
|
|
constexpr size_t kUncompressedPointLength = 65;
|
|
|
|
// The value of the kSecAttrLabel when generating the key. The documentation
|
|
// claims this should be a user-visible label, but there does not exist any UI
|
|
// that shows this value. Therefore, it is left untranslated.
|
|
constexpr char kAttrLabel[] = "Chromium unexportable key";
|
|
|
|
// Returns a span of a CFDataRef.
|
|
base::span<const uint8_t> ToSpan(CFDataRef data) {
|
|
return base::make_span(CFDataGetBytePtr(data),
|
|
base::checked_cast<size_t>(CFDataGetLength(data)));
|
|
}
|
|
|
|
// Copies a CFDataRef into a vector of bytes.
|
|
std::vector<uint8_t> CFDataToVec(CFDataRef data) {
|
|
base::span<const uint8_t> span = ToSpan(data);
|
|
return std::vector<uint8_t>(span.begin(), span.end());
|
|
}
|
|
|
|
std::optional<std::vector<uint8_t>> Convertx963ToDerSpki(
|
|
base::span<const uint8_t> x962) {
|
|
// Parse x9.63 point into an |EC_POINT|.
|
|
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 (x962.size() != kUncompressedPointLength ||
|
|
x962[0] != POINT_CONVERSION_UNCOMPRESSED ||
|
|
!EC_POINT_oct2point(p256.get(), point.get(), x962.data(), x962.size(),
|
|
/*ctx=*/nullptr)) {
|
|
LOG(ERROR) << "P-256 public key is not on curve";
|
|
return std::nullopt;
|
|
}
|
|
// Marshal point into a DER SPKI.
|
|
bssl::UniquePtr<EC_KEY> ec_key(
|
|
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
|
|
CHECK(EC_KEY_set_public_key(ec_key.get(), point.get()));
|
|
bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
|
|
CHECK(EVP_PKEY_assign_EC_KEY(pkey.get(), ec_key.release()));
|
|
bssl::ScopedCBB cbb;
|
|
uint8_t* der_bytes = nullptr;
|
|
size_t der_bytes_len = 0;
|
|
CHECK(CBB_init(cbb.get(), /* initial size */ 128) &&
|
|
EVP_marshal_public_key(cbb.get(), pkey.get()) &&
|
|
CBB_finish(cbb.get(), &der_bytes, &der_bytes_len));
|
|
std::vector<uint8_t> ret(der_bytes, der_bytes + der_bytes_len);
|
|
OPENSSL_free(der_bytes);
|
|
return ret;
|
|
}
|
|
|
|
// UnexportableSigningKeyMac is an implementation of the UnexportableSigningKey
|
|
// interface on top of Apple's Secure Enclave.
|
|
class UnexportableSigningKeyMac : public UnexportableSigningKey {
|
|
public:
|
|
UnexportableSigningKeyMac(base::apple::ScopedCFTypeRef<SecKeyRef> key,
|
|
CFDictionaryRef key_attributes)
|
|
: key_(std::move(key)),
|
|
application_label_(
|
|
CFDataToVec(base::apple::GetValueFromDictionary<CFDataRef>(
|
|
key_attributes,
|
|
kSecAttrApplicationLabel))) {
|
|
base::apple::ScopedCFTypeRef<SecKeyRef> public_key(
|
|
AppleKeychainV2::GetInstance().KeyCopyPublicKey(key_.get()));
|
|
base::apple::ScopedCFTypeRef<CFDataRef> x962_bytes(
|
|
AppleKeychainV2::GetInstance().KeyCopyExternalRepresentation(
|
|
public_key.get(), /*error=*/nil));
|
|
CHECK(x962_bytes);
|
|
base::span<const uint8_t> x962_span = ToSpan(x962_bytes.get());
|
|
public_key_spki_ = *Convertx963ToDerSpki(x962_span);
|
|
}
|
|
|
|
~UnexportableSigningKeyMac() override = default;
|
|
|
|
SignatureVerifier::SignatureAlgorithm Algorithm() const override {
|
|
return SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256;
|
|
}
|
|
|
|
std::vector<uint8_t> GetSubjectPublicKeyInfo() const override {
|
|
return public_key_spki_;
|
|
}
|
|
|
|
std::vector<uint8_t> GetWrappedKey() const override {
|
|
return application_label_;
|
|
}
|
|
|
|
std::optional<std::vector<uint8_t>> SignSlowly(
|
|
base::span<const uint8_t> data) override {
|
|
SecKeyAlgorithm algorithm = kSecKeyAlgorithmECDSASignatureMessageX962SHA256;
|
|
if (!SecKeyIsAlgorithmSupported(key_.get(), kSecKeyOperationTypeSign,
|
|
algorithm)) {
|
|
// This is not expected to happen, but it could happen if e.g. the key had
|
|
// been replaced by a key of a different type with the same label.
|
|
LOG(ERROR) << "Key does not support ECDSA algorithm";
|
|
return std::nullopt;
|
|
}
|
|
|
|
NSData* nsdata = [NSData dataWithBytes:data.data() length:data.size()];
|
|
base::apple::ScopedCFTypeRef<CFErrorRef> error;
|
|
base::apple::ScopedCFTypeRef<CFDataRef> signature(
|
|
AppleKeychainV2::GetInstance().KeyCreateSignature(
|
|
key_.get(), algorithm, NSToCFPtrCast(nsdata),
|
|
error.InitializeInto()));
|
|
if (!signature) {
|
|
LOG(ERROR) << "Error signing with key: " << error.get();
|
|
return std::nullopt;
|
|
}
|
|
return CFDataToVec(signature.get());
|
|
}
|
|
|
|
bool IsHardwareBacked() const override { return true; }
|
|
|
|
SecKeyRef GetSecKeyRef() const override { return key_.get(); }
|
|
|
|
private:
|
|
// The wrapped key as returned by the Keychain API.
|
|
const base::apple::ScopedCFTypeRef<SecKeyRef> key_;
|
|
|
|
// The MacOS Keychain API sets the application label to the hash of the public
|
|
// key. We use this to uniquely identify the key in lieu of a wrapped private
|
|
// key.
|
|
const std::vector<uint8_t> application_label_;
|
|
|
|
// The public key in DER SPKI format.
|
|
std::vector<uint8_t> public_key_spki_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
struct UnexportableKeyProviderMac::ObjCStorage {
|
|
NSString* __strong keychain_access_group_;
|
|
NSString* __strong application_tag_;
|
|
};
|
|
|
|
UnexportableKeyProviderMac::UnexportableKeyProviderMac(Config config)
|
|
: access_control_(config.access_control),
|
|
objc_storage_(std::make_unique<ObjCStorage>()) {
|
|
objc_storage_->keychain_access_group_ =
|
|
base::SysUTF8ToNSString(std::move(config.keychain_access_group));
|
|
objc_storage_->application_tag_ =
|
|
base::SysUTF8ToNSString(std::move(config.application_tag));
|
|
}
|
|
UnexportableKeyProviderMac::~UnexportableKeyProviderMac() = default;
|
|
|
|
std::optional<SignatureVerifier::SignatureAlgorithm>
|
|
UnexportableKeyProviderMac::SelectAlgorithm(
|
|
base::span<const SignatureVerifier::SignatureAlgorithm>
|
|
acceptable_algorithms) {
|
|
return base::Contains(acceptable_algorithms, SignatureVerifier::ECDSA_SHA256)
|
|
? std::make_optional(SignatureVerifier::ECDSA_SHA256)
|
|
: std::nullopt;
|
|
}
|
|
|
|
std::unique_ptr<UnexportableSigningKey>
|
|
UnexportableKeyProviderMac::GenerateSigningKeySlowly(
|
|
base::span<const SignatureVerifier::SignatureAlgorithm>
|
|
acceptable_algorithms) {
|
|
return GenerateSigningKeySlowly(acceptable_algorithms, /*lacontext=*/nil);
|
|
}
|
|
|
|
std::unique_ptr<UnexportableSigningKey>
|
|
UnexportableKeyProviderMac::GenerateSigningKeySlowly(
|
|
base::span<const SignatureVerifier::SignatureAlgorithm>
|
|
acceptable_algorithms,
|
|
LAContext* lacontext) {
|
|
// The Secure Enclave only supports elliptic curve keys.
|
|
if (!SelectAlgorithm(acceptable_algorithms)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Generate the key pair.
|
|
SecAccessControlCreateFlags control_flags = kSecAccessControlPrivateKeyUsage;
|
|
switch (access_control_) {
|
|
case UnexportableKeyProvider::Config::AccessControl::kUserPresence:
|
|
// kSecAccessControlUserPresence is documented[1] (at the time of
|
|
// writing) to be "equivalent to specifying kSecAccessControlBiometryAny,
|
|
// kSecAccessControlOr, and kSecAccessControlDevicePasscode". This is
|
|
// incorrect because includingkSecAccessControlBiometryAny causes key
|
|
// creation to fail if biometrics are supported but not enrolled. It also
|
|
// appears to support Apple Watch confirmation, but this isn't documented
|
|
// (and kSecAccessControlWatch is deprecated as of macOS 15).
|
|
//
|
|
// Reported as FB14040169.
|
|
//
|
|
// [1] https://developer.apple.com/documentation/security/
|
|
// secaccesscontrolcreateflags/ksecaccesscontroluserpresence
|
|
control_flags |= kSecAccessControlUserPresence;
|
|
break;
|
|
case UnexportableKeyProvider::Config::AccessControl::kNone:
|
|
// No additional flag.
|
|
break;
|
|
}
|
|
base::apple::ScopedCFTypeRef<SecAccessControlRef> access(
|
|
SecAccessControlCreateWithFlags(
|
|
kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
|
control_flags,
|
|
/*error=*/nil));
|
|
CHECK(access);
|
|
|
|
NSMutableDictionary* key_attributes =
|
|
[NSMutableDictionary dictionaryWithDictionary:@{
|
|
CFToNSPtrCast(kSecAttrIsPermanent) : @YES,
|
|
CFToNSPtrCast(kSecAttrAccessControl) : (__bridge id)access.get(),
|
|
}];
|
|
if (lacontext) {
|
|
key_attributes[CFToNSPtrCast(kSecUseAuthenticationContext)] = lacontext;
|
|
}
|
|
|
|
NSDictionary* attributes = @{
|
|
CFToNSPtrCast(kSecUseDataProtectionKeychain) : @YES,
|
|
CFToNSPtrCast(kSecAttrKeyType) :
|
|
CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom),
|
|
CFToNSPtrCast(kSecAttrKeySizeInBits) : @256,
|
|
CFToNSPtrCast(kSecAttrTokenID) :
|
|
CFToNSPtrCast(kSecAttrTokenIDSecureEnclave),
|
|
CFToNSPtrCast(kSecPrivateKeyAttrs) : key_attributes,
|
|
CFToNSPtrCast(kSecAttrAccessGroup) : objc_storage_->keychain_access_group_,
|
|
CFToNSPtrCast(kSecAttrLabel) : base::SysUTF8ToNSString(kAttrLabel),
|
|
CFToNSPtrCast(kSecAttrApplicationTag) : objc_storage_->application_tag_,
|
|
};
|
|
|
|
base::apple::ScopedCFTypeRef<CFErrorRef> error;
|
|
base::apple::ScopedCFTypeRef<SecKeyRef> private_key(
|
|
AppleKeychainV2::GetInstance().KeyCreateRandomKey(
|
|
NSToCFPtrCast(attributes), error.InitializeInto()));
|
|
if (!private_key) {
|
|
LOG(ERROR) << "Could not create private key: " << error.get();
|
|
return nullptr;
|
|
}
|
|
base::apple::ScopedCFTypeRef<CFDictionaryRef> key_metadata =
|
|
AppleKeychainV2::GetInstance().KeyCopyAttributes(private_key.get());
|
|
return std::make_unique<UnexportableSigningKeyMac>(std::move(private_key),
|
|
key_metadata.get());
|
|
}
|
|
|
|
std::unique_ptr<UnexportableSigningKey>
|
|
UnexportableKeyProviderMac::FromWrappedSigningKeySlowly(
|
|
base::span<const uint8_t> wrapped_key) {
|
|
return FromWrappedSigningKeySlowly(wrapped_key, /*lacontext=*/nil);
|
|
}
|
|
|
|
std::unique_ptr<UnexportableSigningKey>
|
|
UnexportableKeyProviderMac::FromWrappedSigningKeySlowly(
|
|
base::span<const uint8_t> wrapped_key,
|
|
LAContext* lacontext) {
|
|
base::apple::ScopedCFTypeRef<CFTypeRef> key_data;
|
|
|
|
NSMutableDictionary* query = [NSMutableDictionary dictionaryWithDictionary:@{
|
|
CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey),
|
|
CFToNSPtrCast(kSecAttrKeyType) :
|
|
CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom),
|
|
CFToNSPtrCast(kSecReturnRef) : @YES,
|
|
CFToNSPtrCast(kSecReturnAttributes) : @YES,
|
|
CFToNSPtrCast(kSecAttrAccessGroup) : objc_storage_->keychain_access_group_,
|
|
CFToNSPtrCast(kSecAttrApplicationLabel) :
|
|
[NSData dataWithBytes:wrapped_key.data() length:wrapped_key.size()],
|
|
}];
|
|
if (lacontext) {
|
|
query[CFToNSPtrCast(kSecUseAuthenticationContext)] = lacontext;
|
|
}
|
|
AppleKeychainV2::GetInstance().ItemCopyMatching(NSToCFPtrCast(query),
|
|
key_data.InitializeInto());
|
|
CFDictionaryRef key_attributes =
|
|
base::apple::CFCast<CFDictionaryRef>(key_data.get());
|
|
if (!key_attributes) {
|
|
return nullptr;
|
|
}
|
|
base::apple::ScopedCFTypeRef<SecKeyRef> key(
|
|
base::apple::GetValueFromDictionary<SecKeyRef>(key_attributes,
|
|
kSecValueRef),
|
|
base::scoped_policy::RETAIN);
|
|
return std::make_unique<UnexportableSigningKeyMac>(std::move(key),
|
|
key_attributes);
|
|
}
|
|
|
|
bool UnexportableKeyProviderMac::DeleteSigningKeySlowly(
|
|
base::span<const uint8_t> wrapped_key) {
|
|
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
|
|
base::BlockingType::WILL_BLOCK);
|
|
NSDictionary* query = @{
|
|
CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey),
|
|
CFToNSPtrCast(kSecAttrKeyType) :
|
|
CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom),
|
|
CFToNSPtrCast(kSecAttrAccessGroup) : objc_storage_->keychain_access_group_,
|
|
CFToNSPtrCast(kSecAttrApplicationLabel) :
|
|
[NSData dataWithBytes:wrapped_key.data() length:wrapped_key.size()],
|
|
};
|
|
OSStatus result =
|
|
AppleKeychainV2::GetInstance().ItemDelete(NSToCFPtrCast(query));
|
|
return result == errSecSuccess;
|
|
}
|
|
|
|
std::unique_ptr<UnexportableKeyProviderMac> GetUnexportableKeyProviderMac(
|
|
UnexportableKeyProvider::Config config) {
|
|
if (!base::FeatureList::IsEnabled(crypto::kEnableMacUnexportableKeys)) {
|
|
return nullptr;
|
|
}
|
|
CHECK(!config.keychain_access_group.empty())
|
|
<< "A keychain access group must be set when using unexportable keys on "
|
|
"macOS";
|
|
if (![AppleKeychainV2::GetInstance().GetTokenIDs()
|
|
containsObject:CFToNSPtrCast(kSecAttrTokenIDSecureEnclave)]) {
|
|
return nullptr;
|
|
}
|
|
// Inspecting the binary for the entitlement is not available on iOS, assume
|
|
// it is available.
|
|
#if !BUILDFLAG(IS_IOS)
|
|
if (!ExecutableHasKeychainAccessGroupEntitlement(
|
|
config.keychain_access_group)) {
|
|
return nullptr;
|
|
}
|
|
#endif // !BUILDFLAG(IS_IOS)
|
|
return std::make_unique<UnexportableKeyProviderMac>(std::move(config));
|
|
}
|
|
|
|
} // namespace crypto
|