Implemented vpn connection manager on macOS

This commit is contained in:
Simon Hong
2021-07-13 12:59:45 +09:00
parent aef7ac62ea
commit aec22beadd
10 changed files with 351 additions and 15 deletions
+10
View File
@@ -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>
+10 -1
View File
@@ -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*)&copy_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)
}
+2 -2
View File
@@ -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.