Implemented vpn connection manager on macOS
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.vpn.api</key>
|
||||
<array>
|
||||
<string>allow-vpn</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -33,6 +33,15 @@ static_library("brave_vpn") {
|
||||
|
||||
libs += [ "rasapi32.lib" ]
|
||||
}
|
||||
|
||||
if (is_mac) {
|
||||
sources += [
|
||||
"brave_vpn_connection_manager_mac.h",
|
||||
"brave_vpn_connection_manager_mac.mm",
|
||||
]
|
||||
|
||||
frameworks = [ "NetworkExtension.framework" ]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("brave_vpn_internal") {
|
||||
@@ -61,6 +70,6 @@ executable("vpntool") {
|
||||
]
|
||||
|
||||
if (is_win) {
|
||||
sources = [ "winvpntool.cc" ]
|
||||
sources += [ "winvpntool.cc" ]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,15 @@ namespace brave_vpn {
|
||||
BraveVPNConnectionInfo::BraveVPNConnectionInfo() = default;
|
||||
BraveVPNConnectionInfo::~BraveVPNConnectionInfo() = default;
|
||||
|
||||
void BraveVPNConnectionInfo::SetConnectionInfo(
|
||||
const std::string& connection_name,
|
||||
const std::string& hostname,
|
||||
const std::string& username,
|
||||
const std::string& password) {
|
||||
connection_name_ = connection_name;
|
||||
hostname_ = hostname;
|
||||
username_ = username;
|
||||
password_ = password;
|
||||
}
|
||||
|
||||
} // namespace brave_vpn
|
||||
|
||||
@@ -10,14 +10,26 @@
|
||||
|
||||
namespace brave_vpn {
|
||||
|
||||
struct BraveVPNConnectionInfo {
|
||||
std::string connection_name;
|
||||
std::string hostname;
|
||||
std::string username;
|
||||
std::string password;
|
||||
|
||||
class BraveVPNConnectionInfo {
|
||||
public:
|
||||
BraveVPNConnectionInfo();
|
||||
~BraveVPNConnectionInfo();
|
||||
|
||||
void SetConnectionInfo(const std::string& connection_name,
|
||||
const std::string& hostname,
|
||||
const std::string& username,
|
||||
const std::string& password);
|
||||
|
||||
std::string connection_name() const { return connection_name_; }
|
||||
std::string hostname() const { return hostname_; }
|
||||
std::string username() const { return username_; }
|
||||
std::string password() const { return password_; }
|
||||
|
||||
private:
|
||||
std::string connection_name_;
|
||||
std::string hostname_;
|
||||
std::string username_;
|
||||
std::string password_;
|
||||
};
|
||||
|
||||
} // namespace brave_vpn
|
||||
|
||||
@@ -18,6 +18,8 @@ class BraveVPNConnectionManager {
|
||||
public:
|
||||
class Observer : public base::CheckedObserver {
|
||||
public:
|
||||
// TODO(simonhong): Don't need |name| parameter because only one vpn
|
||||
// connection is managed.
|
||||
virtual void OnCreated(const std::string& name) = 0;
|
||||
virtual void OnRemoved(const std::string& name) = 0;
|
||||
virtual void OnConnected(const std::string& name) = 0;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_CONNECTION_MANAGER_MAC_H_
|
||||
#define BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_CONNECTION_MANAGER_MAC_H_
|
||||
|
||||
#import <NetworkExtension/NetworkExtension.h>
|
||||
#include <string>
|
||||
|
||||
#include "base/no_destructor.h"
|
||||
#include "brave/components/brave_vpn/brave_vpn_connection_manager.h"
|
||||
|
||||
namespace brave_vpn {
|
||||
|
||||
class BraveVPNConnectionManagerMac : public BraveVPNConnectionManager {
|
||||
public:
|
||||
BraveVPNConnectionManagerMac(const BraveVPNConnectionManagerMac&) = delete;
|
||||
BraveVPNConnectionManagerMac& operator=(const BraveVPNConnectionManagerMac&) =
|
||||
delete;
|
||||
|
||||
protected:
|
||||
friend class base::NoDestructor<BraveVPNConnectionManagerMac>;
|
||||
|
||||
BraveVPNConnectionManagerMac();
|
||||
~BraveVPNConnectionManagerMac() override;
|
||||
|
||||
private:
|
||||
// BraveVPNConnectionManager overrides:
|
||||
void CreateVPNConnection(const BraveVPNConnectionInfo& info) override;
|
||||
void UpdateVPNConnection(const BraveVPNConnectionInfo& info) override;
|
||||
void RemoveVPNConnection(const std::string& name) override;
|
||||
void Connect(const std::string& name) override;
|
||||
void Disconnect(const std::string& name) override;
|
||||
|
||||
BraveVPNConnectionInfo info_;
|
||||
};
|
||||
|
||||
} // namespace brave_vpn
|
||||
|
||||
#endif // BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_CONNECTION_MANAGER_MAC_H_
|
||||
@@ -0,0 +1,249 @@
|
||||
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/components/brave_vpn/brave_vpn_connection_manager_mac.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/mac/bundle_locations.h"
|
||||
#include "base/mac/foundation_util.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
|
||||
// Referenced GuardianConnect implementation.
|
||||
// https://github.com/GuardianFirewall/GuardianConnect
|
||||
namespace brave_vpn {
|
||||
|
||||
namespace {
|
||||
|
||||
const NSString* kBraveVPNKey = @"BraveVPNKey";
|
||||
|
||||
NSData* GetPasswordRefForAccount(const NSString* account_key) {
|
||||
NSString* bundle_id = [[NSBundle mainBundle] bundleIdentifier];
|
||||
CFTypeRef copy_result = NULL;
|
||||
NSDictionary* query = @{
|
||||
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
|
||||
(__bridge id)kSecAttrService : bundle_id,
|
||||
(__bridge id)kSecAttrAccount : account_key,
|
||||
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne,
|
||||
(__bridge id)kSecReturnPersistentRef : (__bridge id)kCFBooleanTrue,
|
||||
};
|
||||
OSStatus results = SecItemCopyMatching((__bridge CFDictionaryRef)query,
|
||||
(CFTypeRef*)©_result);
|
||||
if (results != errSecSuccess)
|
||||
LOG(ERROR) << "Error: obtaining password ref(status:" << results << ")";
|
||||
return (__bridge NSData*)copy_result;
|
||||
}
|
||||
|
||||
OSStatus RemoveKeychanItemForAccount(const NSString* account_key) {
|
||||
NSString* bundle_id = [[NSBundle mainBundle] bundleIdentifier];
|
||||
NSDictionary* query = @{
|
||||
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
|
||||
(__bridge id)kSecAttrService : bundle_id,
|
||||
(__bridge id)kSecAttrAccount : account_key,
|
||||
(__bridge id)kSecReturnPersistentRef : (__bridge id)kCFBooleanTrue,
|
||||
};
|
||||
OSStatus result = SecItemDelete((__bridge CFDictionaryRef)query);
|
||||
if (result != errSecSuccess && result != errSecItemNotFound)
|
||||
LOG(ERROR) << "Error: deleting password entry(status:" << result << ")";
|
||||
return result;
|
||||
}
|
||||
|
||||
OSStatus StorePassword(const NSString* password, const NSString* account_key) {
|
||||
if (password == nil) {
|
||||
LOG(ERROR) << "Error: password is empty";
|
||||
return errSecParam;
|
||||
}
|
||||
|
||||
CFTypeRef result = NULL;
|
||||
NSString* bundle_id = [[NSBundle mainBundle] bundleIdentifier];
|
||||
NSData* password_data = [password dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSDictionary* sec_item = @{
|
||||
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
|
||||
(__bridge id)kSecAttrService : bundle_id,
|
||||
(__bridge id)kSecAttrSynchronizable : (__bridge id)kCFBooleanFalse,
|
||||
(__bridge id)kSecAttrAccount : account_key,
|
||||
(__bridge id)kSecValueData : password_data,
|
||||
};
|
||||
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)sec_item, &result);
|
||||
if (status != errSecSuccess) {
|
||||
if (status == errSecDuplicateItem) {
|
||||
VLOG(2) << "There is duplicated key in keychain. removing and re-adding.";
|
||||
if (RemoveKeychanItemForAccount(account_key) == errSecSuccess)
|
||||
return StorePassword(password, account_key);
|
||||
}
|
||||
LOG(ERROR) << "Error: storing password";
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NEVPNProtocolIKEv2* CreateProtocolConfig(const BraveVPNConnectionInfo& info) {
|
||||
NSString* hostname = [NSString stringWithUTF8String:info.hostname().c_str()];
|
||||
NSString* username = [NSString stringWithUTF8String:info.username().c_str()];
|
||||
|
||||
NEVPNProtocolIKEv2* protocol_config = [[NEVPNProtocolIKEv2 alloc] init];
|
||||
protocol_config.serverAddress = hostname;
|
||||
protocol_config.serverCertificateCommonName = hostname;
|
||||
protocol_config.remoteIdentifier = hostname;
|
||||
protocol_config.enablePFS = YES;
|
||||
protocol_config.disableMOBIKE = NO;
|
||||
protocol_config.disconnectOnSleep = NO;
|
||||
protocol_config.authenticationMethod =
|
||||
NEVPNIKEAuthenticationMethodCertificate; // to validate the server-side
|
||||
// cert issued by LetsEncrypt
|
||||
protocol_config.certificateType = NEVPNIKEv2CertificateTypeECDSA256;
|
||||
protocol_config.useExtendedAuthentication = YES;
|
||||
protocol_config.username = username;
|
||||
protocol_config.passwordReference = GetPasswordRefForAccount(kBraveVPNKey);
|
||||
protocol_config.deadPeerDetectionRate =
|
||||
NEVPNIKEv2DeadPeerDetectionRateLow; /* increase DPD tolerance from default
|
||||
10min to 30min */
|
||||
protocol_config.useConfigurationAttributeInternalIPSubnet = false;
|
||||
|
||||
[[protocol_config IKESecurityAssociationParameters]
|
||||
setEncryptionAlgorithm:NEVPNIKEv2EncryptionAlgorithmAES256];
|
||||
[[protocol_config IKESecurityAssociationParameters]
|
||||
setIntegrityAlgorithm:NEVPNIKEv2IntegrityAlgorithmSHA384];
|
||||
[[protocol_config IKESecurityAssociationParameters]
|
||||
setDiffieHellmanGroup:NEVPNIKEv2DiffieHellmanGroup20];
|
||||
[[protocol_config IKESecurityAssociationParameters]
|
||||
setLifetimeMinutes:1440]; // 24 hours
|
||||
[[protocol_config childSecurityAssociationParameters]
|
||||
setEncryptionAlgorithm:NEVPNIKEv2EncryptionAlgorithmAES256GCM];
|
||||
[[protocol_config childSecurityAssociationParameters]
|
||||
setDiffieHellmanGroup:NEVPNIKEv2DiffieHellmanGroup20];
|
||||
[[protocol_config childSecurityAssociationParameters]
|
||||
setLifetimeMinutes:480]; // 8 hours
|
||||
|
||||
return protocol_config;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
BraveVPNConnectionManager* BraveVPNConnectionManager::GetInstance() {
|
||||
static base::NoDestructor<BraveVPNConnectionManagerMac> s_manager;
|
||||
return s_manager.get();
|
||||
}
|
||||
|
||||
BraveVPNConnectionManagerMac::BraveVPNConnectionManagerMac() = default;
|
||||
BraveVPNConnectionManagerMac::~BraveVPNConnectionManagerMac() = default;
|
||||
|
||||
void BraveVPNConnectionManagerMac::CreateVPNConnection(
|
||||
const BraveVPNConnectionInfo& info) {
|
||||
info_ = info;
|
||||
|
||||
if (StorePassword([NSString stringWithUTF8String:info_.password().c_str()],
|
||||
kBraveVPNKey) != errSecSuccess)
|
||||
return;
|
||||
|
||||
NEVPNManager* vpn_manager = [NEVPNManager sharedManager];
|
||||
[vpn_manager loadFromPreferencesWithCompletionHandler:^(NSError* error) {
|
||||
if (error) {
|
||||
LOG(ERROR) << "Create - loadFromPrefs error: "
|
||||
<< base::SysNSStringToUTF8([error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
|
||||
[vpn_manager setEnabled:YES];
|
||||
[vpn_manager setProtocolConfiguration:CreateProtocolConfig(info_)];
|
||||
[vpn_manager setLocalizedDescription:base::SysUTF8ToNSString(
|
||||
info_.connection_name())];
|
||||
|
||||
[vpn_manager saveToPreferencesWithCompletionHandler:^(NSError* error) {
|
||||
if (error) {
|
||||
LOG(ERROR) << "Create - saveToPrefs error: "
|
||||
<< base::SysNSStringToUTF8([error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
VLOG(2) << "Create - saveToPrefs success";
|
||||
for (Observer& obs : observers_)
|
||||
obs.OnCreated(std::string());
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
void BraveVPNConnectionManagerMac::UpdateVPNConnection(
|
||||
const BraveVPNConnectionInfo& info) {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
void BraveVPNConnectionManagerMac::RemoveVPNConnection(
|
||||
const std::string& name) {
|
||||
NEVPNManager* vpn_manager = [NEVPNManager sharedManager];
|
||||
[vpn_manager loadFromPreferencesWithCompletionHandler:^(NSError* error) {
|
||||
if (error) {
|
||||
LOG(ERROR) << "RemoveVPNConnection - loadFromPrefs: "
|
||||
<< base::SysNSStringToUTF8([error localizedDescription]);
|
||||
} else {
|
||||
[vpn_manager removeFromPreferencesWithCompletionHandler:^(
|
||||
NSError* error) {
|
||||
if (error) {
|
||||
LOG(ERROR) << "RemoveVPNConnection - removeFromPrefs: "
|
||||
<< base::SysNSStringToUTF8([error localizedDescription]);
|
||||
}
|
||||
VLOG(2) << "RemoveVPNConnection - successfully removed";
|
||||
for (Observer& obs : observers_)
|
||||
obs.OnRemoved(std::string());
|
||||
}];
|
||||
}
|
||||
RemoveKeychanItemForAccount(kBraveVPNKey);
|
||||
}];
|
||||
}
|
||||
|
||||
void BraveVPNConnectionManagerMac::Connect(const std::string& name) {
|
||||
NEVPNManager* vpn_manager = [NEVPNManager sharedManager];
|
||||
[vpn_manager loadFromPreferencesWithCompletionHandler:^(NSError* error) {
|
||||
if (error) {
|
||||
LOG(ERROR) << "Connect - loadFromPrefs error: "
|
||||
<< base::SysNSStringToUTF8([error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
|
||||
NEVPNStatus current_status = [[vpn_manager connection] status];
|
||||
// Early return if already connected.
|
||||
if (current_status == NEVPNStatusConnected) {
|
||||
VLOG(2) << "Connect - Already connected";
|
||||
return;
|
||||
}
|
||||
|
||||
NSError* start_error;
|
||||
[[vpn_manager connection] startVPNTunnelAndReturnError:&start_error];
|
||||
if (start_error != nil) {
|
||||
LOG(ERROR) << "Connect - startVPNTunnel error: "
|
||||
<< base::SysNSStringToUTF8([start_error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
|
||||
VLOG(2) << "Successfully connected";
|
||||
for (Observer& obs : observers_)
|
||||
obs.OnConnected(std::string());
|
||||
}];
|
||||
}
|
||||
|
||||
void BraveVPNConnectionManagerMac::Disconnect(const std::string& name) {
|
||||
NEVPNManager* vpn_manager = [NEVPNManager sharedManager];
|
||||
[vpn_manager loadFromPreferencesWithCompletionHandler:^(NSError* error) {
|
||||
if (error) {
|
||||
LOG(ERROR) << "Disconnect - loadFromPrefs: "
|
||||
<< base::SysNSStringToUTF8([error localizedDescription]);
|
||||
return;
|
||||
}
|
||||
|
||||
NEVPNStatus current_status = [[vpn_manager connection] status];
|
||||
if (current_status != NEVPNStatusConnected) {
|
||||
VLOG(2) << "Disconnect - Not connected";
|
||||
return;
|
||||
}
|
||||
|
||||
[[vpn_manager connection] stopVPNTunnel];
|
||||
for (Observer& obs : observers_)
|
||||
obs.OnDisconnected(std::string());
|
||||
}];
|
||||
}
|
||||
|
||||
} // namespace brave_vpn
|
||||
@@ -35,17 +35,17 @@ BraveVPNConnectionManagerWin::~BraveVPNConnectionManagerWin() = default;
|
||||
|
||||
void BraveVPNConnectionManagerWin::CreateVPNConnection(
|
||||
const BraveVPNConnectionInfo& info) {
|
||||
const std::wstring name = base::UTF8ToWide(info.connection_name);
|
||||
const std::wstring host = base::UTF8ToWide(info.hostname);
|
||||
const std::wstring user = base::UTF8ToWide(info.username);
|
||||
const std::wstring password = base::UTF8ToWide(info.password);
|
||||
const std::wstring name = base::UTF8ToWide(info.connection_name());
|
||||
const std::wstring host = base::UTF8ToWide(info.hostname());
|
||||
const std::wstring user = base::UTF8ToWide(info.username());
|
||||
const std::wstring password = base::UTF8ToWide(info.password());
|
||||
|
||||
base::ThreadPool::PostTaskAndReplyWithResult(
|
||||
FROM_HERE, {base::MayBlock()},
|
||||
base::BindOnce(&CreateEntry, name.c_str(), host.c_str(), user.c_str(),
|
||||
password.c_str()),
|
||||
base::BindOnce(&BraveVPNConnectionManagerWin::OnCreated,
|
||||
weak_factory_.GetWeakPtr(), info.connection_name));
|
||||
weak_factory_.GetWeakPtr(), info.connection_name()));
|
||||
}
|
||||
|
||||
void BraveVPNConnectionManagerWin::UpdateVPNConnection(
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
declare_args() {
|
||||
enable_brave_vpn = is_win || is_android
|
||||
# On macOS, vpn is not available w/o signing.
|
||||
enable_brave_vpn = is_win || is_android || (is_mac && is_official_build)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
|
||||
index ec09d0836a976ff1c4c005a928e7feb51640a381..1a864d9c1b044c08aae67001aeb4eb52b531a14e 100644
|
||||
index ec09d0836a976ff1c4c005a928e7feb51640a381..7e60fd7ea39dc9e6f37e95dc39ca0b7fc43a2327 100644
|
||||
--- a/chrome/BUILD.gn
|
||||
+++ b/chrome/BUILD.gn
|
||||
@@ -170,6 +170,7 @@ if (!is_android && !is_mac) {
|
||||
@@ -57,7 +57,7 @@ index ec09d0836a976ff1c4c005a928e7feb51640a381..1a864d9c1b044c08aae67001aeb4eb52
|
||||
|
||||
compile_entitlements("entitlements") {
|
||||
entitlements_templates = [ "app/app-entitlements.plist" ]
|
||||
+ if (is_official_build) { entitlements_templates += [ "app/app-entitlements-chrome.plist" ] }
|
||||
+ if (is_official_build) { entitlements_templates += [ "app/app-entitlements-chrome.plist", "//brave/app/app-entitlements-brave.plist" ] }
|
||||
if (is_chrome_branded) {
|
||||
# These entitlements are bound to the official Google Chrome signing
|
||||
# certificate and will not necessarily work in any other build.
|
||||
|
||||
Reference in New Issue
Block a user